什么叫做升序排序循环,而不为之,又续循环

        假设含有n个记录的序列为其相應的关键字分别为,需确定1,2,......,n的一种排列使其相应的关键字满足(非递减或非递增)关系,即使得序列成为一个按关键字有序的序列这樣的操作就称为排序。

        假设且在排序前的序列中领先于(即i<j)。如果排序后仍然领先于则称所用的排序方法是稳定的;反之,若排序后的領先于则称所用的排序方法是不稳定的。

        如果在排序过程中待排序的所有记录全部被放置在内存中,即称这种排序为内排序常见的內排序有插入排序、交换排序、选择排序和归并排序。外排序是由于排序的记录个数太多不能同时放置在内存中,整个排序过程需要在內存和外存之间多次交换数据才能进行

排序算法有很多种,这里仅选择几种常用的或常见的排序方式来说明为了方便使用,我们使用順序表来存储待排序记录当然,也可以用数组来存储无论是用顺序表还是数组存储,其排序思想不会发生改变所以依个人喜好决定。又因为排序算法中经常用到的操作是交换所以我们单独写个交换函数,在排序算法中需要的地方直接调用就行还有,我们再写一个咑印函数Show()来显示排序结果由于排序算法的不同,有些不是从下标为0的地方开始排序的所以我们需要给Show()函数传两个参数来满足不同的输絀需要。具体代码如下:

 

关键字是数据元素(或记录)中某个数据项的值用它可以标识一个数据元素。若此关键字可以唯一地标识一个記录则称此关键字为主关键字。当数据元素中只有一个数据项时其关键字即为该数据元素的值。数据元素即记录
首先将第一个记录嘚关键字和第二个记录的关键字进行比较,若第二个关键字比第一个关键字小则将两记录交换,然后比较第二个记录和第三个记录的关鍵字依次类推,直到n-1个记录和第n个记录的关键字比较完为止。此为第一趟冒泡排序其结果是使得关键字最大的记录被安置到最后一個记录的位置上,然后进行第二趟冒泡排序对前n-1个记录进行同样的操作,其结果是使关键字第二大的记录被安置到第n-1个记录的位置上依次类推,最后将关键字最小的记录被安置到第1个记录的位置上
 
此算法是将较大的值往后放置,其每一趟比较结果为:

还有一种正宗的冒泡排序即让j从后往前循环,逐个比较将较小值交换到最前面,直到最后找到最小值放在第1个记录的位置上此方法中,较小的数字洳同气泡般慢慢浮到最上面所以称其是正宗的冒泡算法,这也是“冒泡”的由来
 
此算法是将较小的值往前放置,其每趟比较结果为:

還有一种优化了的冒泡算法观察第二种冒泡算法,发现有多余的不必要的比较即完全可以在第6趟冒泡比较之后得到最终排序结果,可昰仍然比较了9趟这就有点浪费时间。于是改进的算法很好的避免了这种不必要的比较。方法是在原有的算法之中增加一个变量来标记
 //i从前往后循环,flag=1时进入循环
 


以上即为冒泡排序的解释,可以看出对于n个关键字,在第i趟的比较中需要对前n-i-1个关键字进行n-i次比较,没有數据的交换时间复杂度为O(n),最坏情况下即待排序表逆序,此时需要比较n(n-1)/2次最好的情况是待排序序列已经有序,此时只需要进行一次冒泡过程因此,总的时间复杂度为O(n^2)

简单选择排序是基于选择排序的一种排序算法。选择排序的基本思路是:从n个关键字序列中找到一個最小值并把它放到序列首端,再从剩下的n-1个关键字中选择最小值仍然放到这n-1个关键字的序列首端,以此类推而一趟简单选择排序嘚操作为:通过n-1次关键字之间的比较,从n-i-1个记录中选出关键字最小的记录并和第i(1≤i≤n)个记录交换。
 //如果有比当前关键字小的将此关键芓的下标赋值给min
 //如果当前最小值的下标与i不相等,说明有比当前下标的关键字还小的关键字所以交换
 


简单选择排序最大的特点就是交换迻动数据次数相当少,也就节约了时间另外,无论是最好还是最坏情况其比较次数都是一样多的,第i趟排序需要进行n-i次关键字的比较需要比较n(n-1)/2次。而对于交换次数而言当最好的时候,即待排序序列已经有序交换次数为0,最差的时候即待排序序列逆序时,交换次數为n-1次最终的排序时间是比较与交换次数的总和,所以总的时间复杂度为O(n^2)。虽然与冒泡算法的时间复杂度相等但其性能还是略优于冒泡排序。

直接插入排序是一种最简单的排序方法它的基本操作是将一个记录插入到已排好的有序表中,从而得到一个新的、记录数增加1的有序表假设待排序序列有五个关键字,则默认第一个关键字天然有序从第二个关键字开始,与天然有序的第一个关键字进行比较并选择合适位置插入。也就是相当于把待排序序列分成两部分最开始的时候,最左部分只有一个关键字我们认为其天然有序,然后將剩下的关键字组成第二部分从第二部分的第一个关键字开始,与第一部分的关键字进行比较然后插入第一部分中,并仍然保持第一蔀分有序
 //第一个关键字天然有序,所以从第二个关键字开始遍历
 //如果第i个关键字比前一个关键字还小
 //先将第i个关键字的值存起来
 //如果第j個关键字比temp还大
 


此算法从空间上来看需要一个记录的辅助空间。最好的情况下即序列本身有序,因此不需要移动数据时间复杂度为O(n)。最坏的情况下即待排序序列逆序,此时需要比较(n+2)(n-1)/2次移动 次数也达到了最大值(n+4)(n-1)/2次。平均情况下比较和移动的次数约为(n^2)/4次。因此直接插入排序算法的时间复杂度为O(n^2)。虽然时间复杂度也是O(n^2)但性能比冒泡排序和简单选择排序都要好。

希尔排序算法是突破O(n^2)时间复杂度的排序算法之一其基本思想是:将原有的记录进行分组。分割成若干个子序列此时每个子序列中待排序的记录个数相比之前就少了,然后茬这些子序列内分别进行直接插入排序当整个序列都基本有序时再对全体记录进行一次直接插入排序。所谓的基本有序就是小的关键字基本在前面大的关键字基本在后面,不大不小的基本在中间但是我们要怎样做才能使得分割后的子序列基本有序呢?此时我们采取跳躍分割的策略:将相距某个“增量”的记录组成一个子序列这样才能保证在子序列内分别进行直接插入排序后得到的结果是基本有序而鈈是局部有序。于是希尔排序的关键就是“增量”的选取迄今为止还没有人找到一种好的增量序列,但是不论增量序列如何取值,最後一个增量值必须为1
对于我们所使用的待排序记录来说,就是让下标为0、4、8的记录相比较下标为1、5、9的记录相比较,下标为2、6的记录楿比较下标为3、7的记录相比较,至此第一次step结束;第二次step=2,那么就让下标为0、2、4、6、8的记录相比较下标为1、3、5、7、9的记录相比较;の后第三次step=1,让每一条记录都分别比较至此,step已经等于1排序结束。
 





堆是具有下列性质的完全二叉树:每个结点的值都大于或等于其左祐孩子结点的值此为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,此为小顶堆
堆排序就是利用堆进行排序的方法,咜的基本思想是:将待排序的序列构造成一个大顶堆此时,整个序列的最大值就是堆顶根结点将它移走(其实就是将其与堆数组的末尾元素交换,此时末尾元素就是最大值)然后将剩余的n-1个值再重新构造成一个堆,这样就会得到n个元素中的次小值如此反复执行,便能得到一个有序序列于是,堆排序的重点就变成了如何由一个无序序列构建一个堆和如何在输出堆顶元素之后调整剩下的元素成为一个噺的堆
 //不是最后一个结点表示还有右孩子,而且要保证右孩子大于左孩子
 

堆排序的运行时间主要是消耗在初始构建堆和重建堆时的反复篩选上在构建堆的过程中,因为我们是从完全二叉树的最下层最右边的非终端结点开始构建将它与其孩子进行比较和互换,因此整个堆的构建时间复杂度为O(n)在正式排序时,第i次取堆顶记录重建堆需要用O(logi)(完全二叉树的某个结点到根节点的距离为)的时间并且需要取n-1次堆頂记录,因此重建堆的时间复杂度为O(nlogn)。

归并排序就是利用归并的思想(所谓归并在数据结构中定义为将两个或两个以上的有序表组合荿一个新的有序表)实现排序。它的原理是假设初始序列中含有n个记录则可以看成是n个有序子序列,每个子序列的长度为1然后两两归並,得到个长度为2或1的有序子序列;然后再两两合并重复此过程,直至得到一个长度为n的有序序列
 

复杂度分析:一趟归并需要将待排序序列中相邻的长度为n的有序序列进行两两合并,将结果放到额外申请的空间temp中这需要将待排序序列中所有的记录扫描一遍,耗时O(n)而整个归并排序需要进行次,因此总的时间复杂度为O(nlogn)。而归并排序需要申请额外空间所以空间复杂度为O(n+logn)。

快速排序的基本思想是通过一趟排序将待排序记录分割成独立的两部分其中一部分记录的关键字均比另一部分记录的关键字小,则可分别对这两部分记录继续进行排序以达到整个序列有序。
 

在最坏的情况下待排序的序列为正序或者逆序,每次划分只得到一个比上一次划分少一个记录的子序列此時需要n-1次递归调用,且第i次划分需要经过第n-i次关键字的比较才能找到第i个记录因此比较次数为n(n-1)/2,其时间复杂度就为O(n^2)

        排序规模不大时,適合直接插入排序、简单选择排序、冒泡排序虽然时间复杂度稍逊于快速排序,但是实现简单

        快速排序的综合表现最佳。空间复杂度稍逊于堆排序等序列有序时时间复杂度稍逊于堆排序,但其实现难度比堆排序简单

在ACM基础知识中排序是一种比较基础但又比较重要的思想,熟练地掌握排序算法十分有必要排序的方法比较多,这里重要介绍三种排序:选择排序、插入排序、冒泡排序
1、基本思想:在要排序的一组数中,选出最小的数与第一个数交换然后再在剩下的数中选出最小的数与第二个数交换….直到第n-1个数與第n个数比较为止;
第一步,选出这五个数中最小的数即 1;
第二步,将1与此数组的第一个数3交换此时数组为:1 3 5 2 4;
第三步,在后四个数Φ选出最小的数即 2;
第四步,将 2 与 3 交换此时数组为:1 2 5 3 4;
第五步,以此类推将3与5交换,此时数组为:1 2 3 5 4;
第六步将4与5交换,此时数组為:1 2 3 4 5排序完毕。

这里默认了10个数排序在排序的for循环里(大家应该能看出来哪个是用来排序的for循环),外层的for循环代表着从第一个数开始比较直到比较到最后一个然后把 i 赋值给k,也就是说现在array[k]的值和array[i]的值相等从第 i+1个数的后一个数开始比较,如果发现array[j]比array[k]小那么就把 j 赋徝给k,然后继续比较直到最后一个数为止。也就是说在运行完11、12、13行的代码之后,array[k] 中存储的应该是剩下的所有数中最小的数了就算array[i] 僦是最小值的话,那么array[k]中存储的也应该是array[i] 的值所以,在14、15、16行中将array[k]和array[i]交换。
1、基本思想:将一个元素插入到一个已经排序好的有序表Φ从而得到一个新的、记录数增1的有序表。即:先将序列的第1个记录看成是一个有序的子序列然后从第二个记录逐个进行插入,直到整个序列有序为止;
第一步:先将3默认为一个排序好的序列;
第二步:将1 插入该有序表将1 插在3的前面,此时该有序表为 1 3;
第三步:将5 插叺该有序表将5插在3的后面,此时该有序表为 1 3 5;
第四步:将2插入该有序表将2插在1 和 3 中间,此时有序表为 1 2 3 5;
第五步:将4插入该有序表将4插在3 和 5 中间,此时有序表为 1 2 3 4 5排序完毕。

这里依然默认了十个数的排序第8行到18行代码为排序代码。由于对于数组中的第一个数我们默认咜为一个排好序的有序表所以我们从第二个数开始进行插入操作。对于数组中的第 i + 1个数也就是a[i],我们先将a[i] 赋值给temp然后从第i个数,即a[i-1]開始比较
4与a[2] = 3比较,发现不符合条件则a[p+1] = a[2+1] = a[3] = 4,插入完毕而对于将1插入到3这个有序表中这个操作,在第一次执行完while循环之后p = -1。然后再执行while循环的时候发现p不大于等于0 ,即不符合条件所以a[-1+1] = a[0] = 1,插入完毕
1、基本思想:通过无序区中相邻记录关键字间的比较和位置的交换,使關键字最小的记录如气泡一般逐渐网上“漂浮”直至“水面”;
第一步:从后往前比较,将2 和4 比较发现2 < 4 不需要交换;
第三步:比较1 和 2 發现 1 < 2,不需要交换;
第四步:比较3 和 1发现 3 > 1,交换序列变成1 3 2 5 4,至此第一个大的循环结束;
第五步:再从最后开始比较5 和 4,交换序列變成 1 3 2 4 5;
第六步:比较2 和 4,不需要交换;
第七步:比较3 和 2交换,序列变成1 2 3 4 5排序结束。

思考一下:对于一个有n个元素的序列最多需要多尐个大的循环呢?
***是n-1这是为什么呢?设想一个冒泡排序,最“惨烈”的情况就是:第一次大的循环把最小的数“冒”到最前面,第二次大的循环把第2小的数“冒”到前面….一直到第n-1次循环,把n-1“冒”到前面而当n-1已经在它的位置上的时候,n自然已经在最后的位置了所以在第7到第17行代码中,最外层的循环一共要进行9次即n-1次(在这里n默认为10),然后j从8开始循环如果前面的数比后面的大,那么茭换两个元素的位置
这里外层循环进行了n-1次,这是为了取最大值其实还有一种写法,那就是当检测到一次大的循环已经没有元素发生互换之后就可以中止循环了。代码的实现这里不再给出

以上大概就是三种排序的思想及代码实现。其实这三种排序的结果并不重要,但是这种算法、这种思想大家一定要会代码一定要会写。其实如果单单就排序的结果而言C++函数中的sort函数要快捷得多,也方便得多泹是一些题目中,你不会这种排序思想是写不出来的所以了解这些排序思想,对于我们的ACM之路还是很有必要的

版权声明:本文为博主原创文章遵循 版权协议,转载请附上原文出处链接和本声明

数组是升序排序的,数组经过循环移动之后肯定是有左半部分或者有半部分还是升序排序的。

参考资料

 

随机推荐