1、2023年11月28日1第第9章章 内排序内排序本章学习内容本章学习内容 9.1 基本概念9.2 插入排序9.3 交换排序9.4 选择排序9.5 归并排序9.6 分配排序9.7 各种内部排序方法的比较和选择2023年11月28日29.1 基本概念基本概念9.1.1 排序介绍排序(Sorting)是计算机的数据处理中一种很重要的运算,同时也是很常用的运算,一般计算机中的数据处理工作20%30%的时间都在进行排序。因此,人们自然已对排序进行了深入细致的研究,并且设计出了一些巧妙的算法。但是,仍然有一些与排序相关的问题尚未解决,适应各种不同要求的新算法也被不断开发出来并得到了改进。由于排序涉及的数据
2、量可能很大,因此,排序可以分为内排序和外排序。所谓内排序,就是所有数据可以全部装入到计算机内存进行的排序。而外排序,是数据量特别大,不能一次性装入内存进行,而必须分批装入内存进行,即在排序过程中还必须借助外存。本章仅讨论一些常用的内排序方法及算法的实现。外排序方法将在下一章介绍。所谓排序,简单地说,就是把一组记录(元素)按照某个域的值的递增(即由小到大)或递减(即由大到小)的次序重新排列的过程。2023年11月28日3例如,在表9-1中,若以每个记录的学号为关键字,按排序码年龄的递增(由小到大)排序,则所有记录的排序结果可简记为:(99006,16),(99003,17),(99001,18)
3、,(99004,18),(99002,19),(99005,20);也可能为:(99006,16),(99003,17),(99004,18),(99001,18),(99002,19),(99005,20);表9-1 学生档案表学号姓名年龄性别99001王晓18男99002林鹏19男99003谢宁17女99004张娟18女99005周涛20男99006李燕16女这两个结果都是表9-1按年龄的递增排序结果。若按排序码姓名来进行递增排序,则得到的排序结果为:(99006,李燕),(99002,林鹏),(99001,王晓),(99003,谢宁),(99004,张娟),(99005,周涛)2023年
4、11月28日49.1.2 基本概念1排序码(Sort Key)作为排序依据的记录中的一个属性。它可以是任何一种可比的有序数据类型,可以是记录的关键字,也可以是任何非关键字。如上例中的学生年龄。在此我们认为对任何一种记录都可找到一个取得它排序码的函数Skey(一个或多个关键字的组合)。2有序表与无序表一组记录按排序码的递增或递减次序排列得到的结果被称为有序表,相应地,把排序前的状态称为无序表。3正序表与逆序表若有序表是按排序码升序排列的,则称为升序表或正序表,否则称为降序表或逆序表。不失普遍性,我们一般只讨论正序表。2023年11月28日54排序定义若给定一组记录序列r1,r2,rn,其排序码分
5、别为s1,s2,sn,将这些记录排成顺序为rk1,rk2,rkn的一个序列R,满足条件sk1sk2 skn,获得这些记录排成顺序为rp1,rp2,rpn的一个序列R,满足条件sp1sp2 spn的过程称为排序。也可以说,将一组记录按某排序码递增或递减排列的过程,称为排序。5稳定与不稳定因为排序码可以不是记录的关键字,同一排序码值可能对应多个记录。对于具有同一排序码的多个记录来说,若采用的排序方法使排序后记录的相对次序不变,则称此排序方法是稳定的,否则称为不稳定的。在上例中(见表9-1,按年龄排序),如果一种排序方法使排序后的结果必为前一个结果,则称此方法是稳定的;若一种排序方法使排序后的结果可
6、能为后一个结果,则称此方法是不稳定的。2023年11月28日66内排序与外排序按照排序过程中使用内外存的不同将排序方法分为内排序和外排序。若排序过程全部在内存中进行,则称为内排序;若排序过程需要不断地进行内存和外存之间的数据交换,则称为外排序(前面的概念中已介绍)。内排序大致可分为五类:插入排序、交换排序、选择排序、归并排序和分配排序。本章仅讨论内排序。7排序的时间复杂性排序过程主要是对记录的排序码进行比较和记录的移动过程。因此排序的时间复杂性可以算法执行中的数据比较次数及数据移动次数来衡量。当一种排序方法使排序过程在最坏或平均情况下所进行的比较和移动次数越少,则认为该方法的时间复杂性就越好。
7、分析一种排序方法,不仅要分析它的时间复杂性,而且要分析它的空间复杂性、稳定性和简单性等。为了以后讨论方便,我们直接将排序码写成一个一维数组的形式,具体类型设为elemtype,并且在没有声明的情形下,所有排序都按排序码的值递增排列。2023年11月28日79.2 插入排序插入排序9.2.1 直接插入排序1直接插入排序的基本思想直接插入排序(Straight Insertion Sorting)的基本思想是:把n个待排序的元素看成一个有序表和一个无序表,开始时有序表中只包含一个元素,无序表中包含n-1个元素,排序过程中每次从无序表中取出第一个元素,把它的排序码依次与有序表元素的排序码进行比较,将
8、它插入到有序表中的适当位置,使之成为新的有序表。2023年11月28日82直接插入的算法实现void insertsort(elemtype R,int n)/待排序元素用一个数组R表示,数组有n个元素 for(int i=1;i=0)&(tempRj)Rj+1=Rj;j-;/顺序比较和移动 Rj+1=temp;2023年11月28日9例如,n=6,数组R的六个排序码分别为:17,3,25,14,20,9。它的直接插入排序的执行过程如图9-1所示。R0 R1 R2 R3 R4 R5初始状态 (17)3 25 14 20 9第一次插入 (3 17)25 14 20 9第二次插入 (3 17 25
9、)14 20 9第三次插入 (3 14 17 25)20 9第四次插入 (3 14 17 20 25)9第五次插入 (3 9 14 17 20 25)图9-1 直接插入排序示例2023年11月28日103直接插入排序的效率分析从上面的叙述可以看出,直接插入排序算法十分简单。那么它的效率如何呢?首先从空间来看,它只需要一个元素的辅助空间,用于元素的位置交换。从时间分析,首先外层循环要进行n-1次插入,每次插入最少比较一次(正序),移动两次;最多比较i次,移动i+2次(逆序)(i=1,2,n-1)。若分别用Cmin,Cmax和Cave表示元素的总比较次数的最小值、最大值和平均值,用Mmin,Mma
10、x和Mave表示元素的总移动次数的最小值、最大值和平均值,则上述直接插入算法对应的这些量为:Cmin=n-1 Mmin=2(n-1)Cmax=1+2+n-1=(n2-n)/2 Mmax=3+4+n+1=(n2+3n-4)/2Cave=(n2+n-2)/4 Mmax=(n2+7n-8)/4因此,直接插入排序的时间复杂度为O(n2)。因为直接插入算法的元素移动是顺序的,因此该方法是稳定的。2023年11月28日119.2.2 二分插入排序1二分插入排序的基本思想二分插入排序(Binary Insert Sorting)的基本思想是:在有序表中采用二分查找的方法查找待排元素的插入位置。其处理过程为:
11、先将第一个元素作为有序序列,进行n-1次插入,用二分查找的方法查找待排元素的插入位置,将待排元素插入。2二分插入排序的算法实现算法如下:void BinaryInsertSort(elemtype R,int n)for(int i=1;in;i+)/i表示插入次数,共进行n-1次插入 int left=0,right=i-1;elemtype temp=Ri;while(left=right)int middle=(left+right)/2;/取中点 if(temp=left;j-)Rj+1=Rj;/元素后移空出插入位 Rleft=temp;2023年11月28日123二分插入排序的效率分
12、析二分插入算法与直接插入算法相比,需要辅助空间,与直接插入排序基本一致;时间上,前者的比较次数比直接插入查找的最坏情况好,最好情况坏,两种方法元素的移动次数相同,因此二分插入排序的时间复杂度仍为O(n2)。二分插入算法与直接插入算法的元素移动一样,是顺序的,因此该方法也是稳定的。9.2.3 希尔排序1希尔排序的基本思想希尔排序(Shell Sorting)又称为“缩小增量排序”,是1959年由D.L.Shell提出来的。该方法的基本思想是:先将整个待排元素序列分割成若干个子序列(由相隔某个“增量”的元素组成的)分别进行直接插入排序,待整个序列中的元素基本有序(增量足够小)时,再对全体元素进行一
13、次直接插入排序。因为直接插入排序在元素基本有序的情况下(接近最好情况),效率是很高的,因此希尔排序在时间效率上比前两种方法有较大提高。2023年11月28日132希尔排序的算法实现下面给出希尔排序算法:void ShellSort(elemtype R,int n)for(int d=n/2;d=1;d/=2)/d表示增量大小,增量每次整除2,第一次为n/2 for(int i=d;i=0;j-=d)/在组内向前顺序进行比较和移动 if(tempRj)Rj+d=Rj;else break;/查找到合适位置就退出j循环 Rj+d=temp;2023年11月28日14例如,n=8,数组R的八个元素
14、分别为:17,3,30,25,14,17,20,9。下面用图9-2给出希尔排序算法的执行过程。17 3 30 25 14 17 20 9 初始状态,i=4 14 3 17 9 20 17 30 25 第二趟结果,i=1 3 9 14 17 17 20 25 30 第三趟结果 14 3 20 9 17 17 30 25 第一趟结果,i=2 图9-2 希尔排序算法的执行过程2023年11月28日153希尔排序的效率分析虽然我们给出的算法是三层循环,最外层循环为log2n数量级,中间的for循环是n数量级的,内循环远远低于n数量级,因为当分组较多时,组内元素较少;此循环次数少;当分组较少时,组内元素
15、增多,但已接近有序,循环次数并不增加。因此,希尔排序的时间复杂度在O(nlog2n)和O(n2)之间,大致为O(n1.3)。由于希尔排序对每个子序列单独比较,在比较时进行元素移动,有可能改变相同排序码元素的原始顺序,因此希尔排序是不稳定的。例如,给定排序码3,4,2,2,则希尔排序的结果变成2,2,3,4。2023年11月28日169.3 交换排序交换排序交换排序的基本思想是两两比较待排序元素的排序码,如发现逆序则交换之,直到所有元素形成有序表。选择比较对象的方法不同,对应有不同的交换排序算法。交换排序主要包括冒泡排序和快速排序。9.3.1 冒泡排序1冒泡排序的基本思想冒泡排序(Bubble
16、Sorting)的基本思想是:对排序序列从后向前(从下标较大的元素开始)依次比较相邻元素的排序码,若发现逆序则交换,使排序码较小的元素逐渐从后部移向前部(从下标较大的单元移向下标较小的单元),就像水底下的气泡一样逐渐向上冒。因为排序的过程中,各元素不断接近自己的位置,如果一趟比较下来没有进行过交换,就说明序列有序,因此要在排序过程中设置一个标志flag判断元素是否进行过交换,从而减少不必要的比较。2023年11月28日172冒泡排序的算法实现下面给出冒泡排序算法:void Bubblesort(elemtype R,int n)int flag=1;/当flag为0则停止排序 for(int
17、i=1;i=i;j-)if(RjRj-1)/发生逆序 elemtype t=Rj;Rj=Rj-1;Rj-1=t;flag=1;/交换,并标记发生了交换 if(flag=0)return;2023年11月28日18例如,n=6,数组R的六个排序码分别为:17,3,25,14,20,9。下面用图9-3给出冒泡排序算法的执行过程。R0 R1 R2 R3 R4 R5 第一趟排序 3 (17 9 25 14 20)第二趟排序 3 9 (17 14 25 20)第三趟排序 3 9 14 (17 20 25)第四趟排序 3 9 14 17 20 25 初始状态 (17 3 25 14 20 9)图9-3 冒
18、泡排序示例2023年11月28日193冒泡排序的效率分析从上面的例子可以看出,当进行完第三趟排序时,数组已经有序,所以第四趟排序的交换标志为0,即没进行交换,所以不必进行第四趟排序。从冒泡排序的算法可以看出,若待排序的元素为正序,则只需进行一趟排序,比较次数为(n-1)次,移动元素次数为0;若待排序的元素为逆序,则需进行n-1趟排序,比较次数为(n2-n)/2,移动次数为3(n2-n)/2,因此冒泡排序算法的时间复杂度为O(n2)。由于其中的元素移动较多,所以属于内排序中速度较慢的一种。因为冒泡排序算法只进行元素间的顺序移动,所以是一个稳定的算法。9.3.2 快速排序1快速排序的基本思想快速排
19、序(Quick Sorting)是迄今为止所有内排序算法中速度最快的一种。它的基本思想是:任取待排序序列中的某个元素作为基准(一般取第一个元素),通过一趟排序,将待排元素分为左右两个子序列,左子序列元素的排序码均小于或等于基准元素的排序码,右子序列的排序码则大于基准元素的排序码,然后分别对两个子序列继续进行排序,直至整个序列有序。快速排序是对冒泡排序的一种改进方法,算法中元素的比较和交换是从两端向中间进行的,排序码较大的元素一次就能够交换到后面单元,排序码较小的记录一次就能够交换到前面单元,记录每次移动的距离较远,因而总的比较和移动次数较少。2023年11月28日20快速排序的过程为:把待排序
20、区间按照第一个元素(即基准元素)的排序码分为左右两个子序列的过程叫做一次划分。设待排序序列为RleftRright,其中left为下限,right为上限,leftright,Rleft为该序列的基准元素,为了实现一次划分,令i,j的初值分别为left和right。在划分过程中,首先让j从它的初值开始,依次向前取值,并将每一元素Rj的排序码同Rleft的排序码进行比较,直到RjRleft时,交换Rj与Rleft的值,使排序码相对较小的元素交换到左子序列,然后让i从i1开始,依次向后取值,并使每一元素Ri的排序码同Rj的排序码(此时Rj为基准元素)进行比较,直到RiRj时,交换Ri与Rj的值,使排
21、序码大的元素交换到后面子区间;再接着让j从j-1开始,依次向前取值,重复上述过程,直到i等于j,即指向同一位置为止,此位置就是基准元素最终被存放的位置。此次划分得到的前后两个待排序的子序列分别为RleftRi-1和Ri+1Rright。例如,给定排序码为:(46,55,13,42,94,05,17,70),具体划分如图9-4所示。从图9-4可知,通过一次划分,将一个区间以基准值分成两个子区间,左子区间的值小于等于基准值,右子区间的值大于基准值。对剩下的子区间重复此划分步骤,则可以得到快速排序的结果。2023年11月28日21 46 55 13 42 94 05 17 70 ij46 55 13
22、 42 94 05 17 70 ij17 55 13 42 94 05 46 70 ij17 46 13 42 94 05 55 70 ij17 05 13 42 94 46 55 70 ij17 05 13 42 94 46 55 70 ij17 05 13 42 94 46 55 70 ij17 05 13 42 46 94 55 70 ij图9-4 快速排序的一次划分2023年11月28日222快速排序的算法实现下面给出快速排序算法的递归算法:void quicksort(elemtype R,int left,int right)int i=left,j=right;ElemType
23、temp=Ri;while(itemp)&(ji)j=j-1;if(ji)Ri=Rj;i=i+1;while(Rii)i=i+1;if(ij)Rj=Ri;j=j-1;/一次划分得到基准值的正确位置Ri=temp;if(lefti-1)quicksort(R,left,i-1);/递归调用左子区间 if(i+1right)quicksort(R,i+1,right);/递归调用右子区间2023年11月28日233快速排序的效率分析在快速排序中,若把每次划分所用的基准元素看成根结点,把划分得到的左子区间和右子区间分别看成根结点的左、右子树,那么整个排序过程就对应着一棵具有n个结点的二叉排序树,所需
24、划分的层数等于二叉树的深度,所需划分的所有区间数等于二叉树分枝结点数,而在快速排序中,元素的移动次数通常小于元素的比较次数。因此,在讨论快速排序的时间复杂度时,仅考虑元素的比较次数即可。若快速排序出现最好的情形(左、右子区间的长度大致相等),则结点数n与二叉树深度h应满足log2nhlog2n+1,所以总的比较次数不会超过(n+1)log2n。因此,快速排序的最好时间复杂度应为O(nlog2n)。而且在理论上已经证明,快速排序的平均时间复杂度也为O(nlog2n)。若快速排序出现最坏的情形(每次能划分成两个子区间,但其中一个是空),则这时得到的二叉树是一棵单分枝树,得到的非空子区间包含n-i个
25、元素(i代表二叉树的层数,1in),每层划分需要比较n-i+2次,所以总的比较次数为(n2+3n-4)/2。因此,快速排序的最坏时间复杂度为O(n2)。快速排序所占用的辅助空间为栈的深度,故最好的空间复杂度为O(log2n),最坏的空间复杂度为O(n)。快速排序是一种不稳定的排序方法。2023年11月28日249.4 选择排序选择排序9.4.1 直接选择排序1直接选择排序的基本思想直接选择排序(Straight Select Sorting)也是一种简单的排序方法。它的基本思想是:第一次从R0Rn-1中选取最小值,与R0交换,第二次从R1Rn-1中选取最小值,与R1交换,第三次从R2Rn-1中
26、选取最小值,与R2交换,第i次从Ri-1Rn-1中选取最小值,与Ri-1交换,第n-1次从Rn-2Rn-1中选取最小值,与Rn-2交换,总共通过n-1次,得到一个按排序码从小到大排列的有序序列。2023年11月28日25例如,给定n=8,数组R中的8个元素的排序码为:(8,3,2,1,7,4,6,5),则直接选择排序过程如图9-5所示。初始状态 8 3 2 1 7 4 6 5 第一次 1 3 2 8 7 4 6 5 第二次 1 2 3 8 7 4 6 5 第三次 1 2 3 8 7 4 6 5 第四次 1 2 3 4 7 8 6 5 第五次 1 2 3 4 5 8 6 7 第六次 1 2 3
27、4 5 6 8 7 第七次 1 2 3 4 5 6 7 8 图9-5 直接选择排序的过程示例2023年11月28日262直接选择排序的算法实现void selectsort(elemtype R,int n)int i,j,m;elemtype t;for(i=0;in-1;i+)m=i;for(j=i+1;jn;j+)if(RjRm)m=j;if(m!=i)t=Ri;Ri=Rm;Rm=t;2023年11月28日273直接选择排序的效率分析在直接选择排序中,共需进行n-1次选择和交换,每次选择需要进行n-i次比较(1in-1),而每次交换最多需要3次移动,因此,总的比较次数C=,总的移动次数M
28、=3(n-1)。112)(21)1(ninnn113ni由此可知,直接选择排序的时间复杂度为O(n2)数量级,所以当记录占用的字节数较多时,通常比直接插入排序的执行速度要快一些。由于在直接选择排序中存在着不相邻元素之间的互换,因此,直接选择排序是一种不稳定的排序方法。例如,给定排序码为3,7,3,2,1,排序后的结果为1,2,3,3,7。2023年11月28日289.4.2 树形选择排序从直接选择排序可知,在n个排序码中,找出最小值需n-1次比较,找出第二小值需n-2次比较,找出第三小值需n-3次比较,其余依此类推。所以,总的比较次数为:(n-1)+(n-2)+3+2+1=(n2-n)/2,那
29、么,能否对直接选择排序算法加以改进,使总的比较次数比(n2-n)/2小呢?显然,在n个排序码中选出最小值,至少进行n-1次比较,但是,继续在剩下的n-1个关键字中选第二小的值,就并非一定要进行n-2次比较,若能利用前面n-1次比较所得的信息,则可以减少以后各趟选择排序中所用的比较次数,比如8个运动员中决出前三名,不需要7+6+5=18场比赛(前提是,若甲胜乙,而乙胜丙,则认为甲胜丙),最多需要11场比赛即可(通过7场比赛产生冠军后,第二名只能在输给冠军的三个队中产生,需两场比赛,而第三名只能在输给亚军的三个队中产生,也需两场比赛,总共需要11场比赛)。具体如图9-6所示。2023年11月28日
30、29 A A E A A C C D B F B B G H G(a)8个队决出冠军的情形(共7场比赛)B*E E C C C D B F B B G H G(b)决出亚军的情形(共两场比赛,少于6 场)C*E E C C C D*F F F G H G(c)决出第三名的情形(共两场比赛,少于6场)图9-6 决出比赛前三名的过程示意图2023年11月28日30从图9-6(a)可知,8个队经过4场比赛,获胜的4个队进入半决赛,再经过两场半决赛和1场决赛即可知道冠军是谁(共7场比赛),按照锦标赛的传递关系,亚军只能分别从在决赛、半决赛和第一轮比赛中输给冠军的队中选取,于是亚军只能在b、c、e这3个
31、队中产生(见图9-6(b),即进行两场比赛(e与c一场,e与c的胜队与b一场)后,即可知道亚军是谁。同理,第三名只需在c、f、g这3个队中产生(见图9-6(c),即进行两场比赛(f与g一场,f与g的胜队与c一场),即可知道第三名是谁。树形选择排序(Tree Selection Sorting),又称锦标赛排序(Tournament Sorting),是一种按照锦标赛的思想进行选择排序的方法。首先对n个记录的排序码进行两两比较,然后在其中n/2个较小者之间再进行两两比较,如此重复,直到选出最小排序码为止。例如,给定排序码50,37,66,98,75,12,26,49,树形选择排序的过程见图9-7
32、。2023年11月28日31 66 12 50 37 37 12 26 12 37 66 98 75 12 26 49 66 26 50 37 37 75 26 26 37 66 98 75 26 49 (a)经过7次比较得到最小值12(b)输出12后,经过两次比较得到第二小值2666 49 50 37 37 75 49 37 37 66 98 75 49 66 49 50 50 50 75 49 49 66 98 75 49(c)输出12,26后,经过两次比较得到第三小值37(d)输出12,26,37后,经过两次比较得到第四小值49 2023年11月28日3266 75 50 50 50 7
33、5 50 66 98 75 66 75 66 75 66 66 98 75 (e)输出12,26,37,49后,经过1次比较得到第五小值50(f)输出12,26,37,49,50后,经过1次比较得到第六小值66 98 75 98 75 75 98 75 98 98 98 98 (g)输出12,26,37,49,50,66后,经过1次比较得到第七小值75(h)输出12,26,37,49,50,66,75后,经过1次比较得到第八小值98图9-7 树形选择排序过程示意2023年11月28日33在树形选择排序中,含有n个叶子结点的完全二叉树的深度为log2n+1,除了最小排序码外,每选择一次小排序码时
34、,仅需进行log2n 次比较,因此,该排序算法的时间复杂度为O(nlog2n)。但是,这种排序方法需要用很多临时指针来保存比较的中间信息,占用较多的辅助单元,故树形选择排序一般很少采用,而一般采用另一种形式的选择排序堆排序。9.4.3 堆排序1堆的定义若有n个元素的排序码k1,k2,k3,kn,当满足如下条件:kik2i kik2i(1)或 (2)kik2i+1 kik2i+1 其中i=1,2,n/2则称此n个元素的排序码k1,k2,k3,kn为一个堆。2023年11月28日34若将此排序码按顺序组成一棵完全二叉树,则(1)称为小根堆(二叉树的所有根结点值小于或等于左右孩子的值),(2)称为大
35、根堆(二叉树的所有根结点值大于或等于左右孩子的值)。若n个元素的排序码k1,k2,k3,kn满足堆,且让结点按1,2,3,n顺序编号,根据完全二叉树的性质(若i为根结点,则左孩子为2i,右孩子为2i+1)可知,堆排序实际与一棵完全二叉树有关。若将排序码初始序列组成一棵完全二叉树,则堆排序可以包含建立初始堆(使排序码变成能符合堆的定义的完全二叉树)和利用堆进行排序两个阶段。2堆排序的基本思想将排序码k1,k2,k3,kn表示成一棵完全二叉树,然后从第n/2个排序码开始筛选,使由该结点作根结点组成的子二叉树符合堆的定义,然后从第n/2-1个排序码重复刚才的操作,直到第一个排序码止。这时,该二叉树符
36、合堆的定义,初始堆已经建立。接着,可以按如下方法进行堆排序:将堆中第一个结点(二叉树根结点)和最后一个结点的数据进行交换(k1与kn),再将k1kn-1重新建堆,然后k1和kn-1交换,再将k1kn-2重新建堆,然后k1和kn-2交换,如此重复下去,每次重新建堆的元素个数不断减1,直到重新建堆的元素个数仅剩一个为止。这时堆排序已经完成,则排序码k1,k2,k3,kn已排成一个有序序列。2023年11月28日35若排序是从小到大排列,则可以用建立大根堆实现堆排序,若排序是从大到小排列,则可以用建立小根堆实现堆排序。例如,给定排序码46,55,13,42,94,05,17,70,建立初始堆的过程如
37、图9-8所示。4655429470130517465570944213051746557094421705134694705542170513(a)初始无序的结点,从42开始调整(b)将以13为根的子树调整成堆(c)将以55为根的子树调整成堆 (d)将以46为根的子树调整成堆2023年11月28日36 9470465542170513(e)成堆图9-8 建立初始大根堆的过程示意图对排序码46,55,13,42,94,05,17,70,建成如图9-8(e)所示的大根堆后,堆排序的过程如图9-9所示。2023年11月28日3794 70 46 55 42 17 05 13 42 70 46 55
38、94 17 05 13(a)初始堆 (b)94与42交换 70554642941705131355464294170570(c)前7个排序码重新建成堆 (d)70和13交换2023年11月28日38 55 46 13 42 94 17 05 70 05 46 13 42 94 17 55 70(e)前6个排序码重新建成堆 f)55和05交换46 42 13 05 94 17 55 70 05 42 13 46 94 17 55 70 (g)前5个排序码重新建成堆 (h)46和05交换 2023年11月28日3942 13 05 46 94 17 55 70 05 13 42 46 94 17
39、55 70 (i)前4个排序码重新建成堆 (j)42和05交换 17 13 42 46 94 05 55 70 05 13 42 46 94 17 55 70 (k)前3个排序码重新建成堆 (l)17和05交换 2023年11月28日4013 05 42 46 94 17 55 70 05 13 42 46 94 17 55 70(m)前2个排序码重新建成堆 (n)13和05交换 图9-9 堆排序过程示意图 从图9-9(n)可知,将其结果按完全二叉树形式输出,则得到的结果为:05,13,17,42,46,55,70,94,即为堆排序的结果。2023年11月28日413堆排序的算法实现void
40、creatheap(ElemType R,int i,int n)/建立大根堆 int j;ElemType t;t=Ri;j=2*i;while(jn)if(jn)&(RjRj+1)j+;if(t=0;i-)creatheap(R,i,n);for(i=n-1;i=0;i-)t=R0;R0=Ri;Ri=t;creatheap(R,0,i-1);4堆排序的效率分析在整个堆排序中,共需进行n+n/2-1次筛选运算,每次筛选运算进行双亲和孩子或兄弟结点的排序码的比较和移动次数都不会超过完全二叉树的深度,所以,每次筛选运算的时间复杂度为O(log2n),故整个堆排序过程的时间复杂度为O(nlog2n
41、)。堆排序占用的辅助空间为1(供交换元素用),故它的空间复杂度为O(1)。堆排序是一种不稳定的排序方法,例如,给定排序码:2,1,2,它的排序结果为:1,2,2。2023年11月28日439.5 归并排序归并排序9.5.1 二路归并排序1二路归并排序的基本思想二路归并排序的基本思想是:将两个有序子区间(有序表)合并成一个有序子区间,一次合并完成后,有序子区间的数目减少一半,而区间的长度增加一倍,当区间长度从1增加到n(元素个数)时,整个区间变为一个,则该区间中的有序序列即为我们所需的排序结果。例如,给定排序码46,55,13,42,94,05,17,70,二路归并排序过程如图9-10所示。三趟
42、归并:05 13 17 42 46 55 70 94 二趟归并:13 42 46 55 05 17 70 94 一趟归并:46 55 13 42 05 94 17 70初始状态:46 55 13 42 94 05 17 70图9-10 二路归并排序过程示意图2023年11月28日442二路归并排序的算法实现二路归并排序包含两两归并排序、一趟归并排序和二路归并排序三部分。两两归并排序是将两个有序子区间合并成一个有序子区间,一趟归并排序是对相同长度的有序子区间进行两两归并,使子区间的数目减少,区间的长度增加。void merge(elemtype R,elemtype A,int s,int m,
43、int t)/将两个子区间RsRm和Rm+1Rt合并,结果存入A中 int i,j,k;i=s;j=m+1;k=s;while(i=m)&(j=t)if(Ri=Rj)Ak=Ri;i+;k+;else Ak=Rj;j+;k+;while(i=m)/复制第一个区间中剩下的元素 Ak=Ri;i+;k+;while(j=t)/复制第二个区间中剩下的元素 Ak=Rj;j+;k+;2023年11月28日45void mergepass(elemtype R,elemtype A,int n,int c)/对R数组做一趟归并,结果存入A数组中,n为元素个数,c为区间长度 int i,j;i=0;while(
44、i+2*c-1=n-1)/长度均为c的两个区间合并成一个区间 merge(R,A,i,i+c-1,i+2*c-1);i+=2*c;if(i+c-1n)/长度不等的两个区间合并成一个区间 merge(R,A,i,i+c-1,n-1);else for(j=i;j=n-1;j+)/仅剩一个区间时,直接复制到A中 Aj=Rj;void mergesort(elemtype R,int n)int c=1;elemtype A100;while(cn)mergepass(R,A,n,c);/一次合并,结果存入A中 c*=2;/区间长度扩大一倍 mergepass(A,R,n,c);/再次合并,结果存入
45、R中 c*=2;2023年11月28日463二路归并排序的效率分析二路归并排序的时间复杂度等于归并趟数与每一趟时间复杂度的乘积。而归并趟数为log2n(当log2n为奇数时,则为log2n+1)。因为每一趟归并就是将两两有序子区间合并成一个有序子区间,而每一对有序子区间归并时,记录的比较次数均小于等于记录的移动次数(即由一个数组复制到另一个数组中的记录个数),而记录的移动次数等于这一对有序表的长度之和,所以,每一趟归并的移动次数均等于数组中记录的个数n,即每一趟归并的时间复杂度为O(n)。因此,二路归并排序的时间复杂度为O(nlog2n)。利用二路归并排序时,需要利用与待排序数组相同的辅助数组
46、作临时单元,故该排序方法的空间复杂度为O(n),比前面介绍的其他排序方法占用的空间大。由于二路归并排序中,每两个有序表合并成一个有序表时,若分别在两个有序表中出现相同的排序码,则会使前一个有序表中相同的排序码先复制,后一有序表中相同的排序码后复制,从而保持它们的相对次序不会改变。所以,二路归并排序是一种稳定的排序方法。9.5.2 多路归并排序将三个或三个以上的有序子区间合并成一个有序子区间的排序,称为多路归并排序。常见的有三路归并排序(三个有序子区间合并成一个有序子区间)、四路归并排序(四个有序子区间合并成一个有序子区间)等,具体实现的方法与二路归并排序类似,在此不再赘述。2023年11月28
47、日479.6 分配排序分配排序9.6.1 多关键字排序在实际应用中,有时的排序会需要按几种不同排序码来排序。例如,描述一个单位的职工信息,既要按出生日期排序,又要按工资排序,则是一种典型的多关键字排序。又如,将一副扑克牌中的52张牌按从小到大排列,(规定花色大小为:梅花方块红桃黑桃,面值大小规定为:23410JQK=1;i-)/对d个关键字进行循环 for(j=c1;j=crd;j+)fj=0;while(p!=0)/分配算法 j=rp.keyi;if(fj=0)fj=p;else rej.next=p;ej=p;p=rp.next;j=c1;while(fj=0)j+;p=fj;t=ej;w
48、hile(jcrd)/收集算法 j+;while(jcrd)&(fj=0)j+;if(fj!=0)rt.next=fj;t=ej;rt.next=0;return p;2023年11月28日533基数排序的效率分析对于含有n个元素的关键字(每个关键字有d个排序码,每个排序码的取值范围从c1到 crd),每一趟分配和收集的时间复杂度为O(n+crd-c1+1),由于有d个排序码,故需进行d趟分配和收集,因此,基数排序的时间复杂度为O(d(n+crd-c1+1)。在基数排序中,由于每个排序码的取值范围从c1到 crd,故需要crd-c1+1个队头和队尾指针,故它的空间复杂度为O(n+crd-c1+
49、1)。由于基数排序中值相同的元素的相对位置在分配和收集中不会发生变化,所以基数排序是一种稳定的排序方法。2023年11月28日549.7 各种内排序方法的比较和选择各种内排序方法的比较和选择9.7.1 各种内排序方法的比较1从时间复杂度比较从平均时间复杂度来考虑,直接插入排序、冒泡排序、直接选择排序是三种简单的排序方法,时间复杂度都为O(n2),而快速排序、堆排序、二路归并排序的时间复杂度都为O(nlog2n),希尔排序的时间复杂度介于这两者之间。若从最好的时间复杂度考虑,则直接插入排序和冒泡排序的时间复杂度最好为O(n),其他的最好情形同平均情形相同。若从最坏的时间复杂度考虑,则快速排序的为
50、O(n2),直接插入排序、冒泡排序、希尔排序同平均情形相同,但系数大约增加一倍,所以运行速度将降低一半,最坏情形对直接选择排序、堆排序和归并排序影响不大。2从空间复杂度比较归并排序的空间复杂度最大,为O(n),快速排序的空间复杂度为O(log2n),其他排序的空间复杂度为O(1)。2023年11月28日553从稳定性比较直接插入排序、冒泡排序、归并排序是稳定的排序方法,而直接选择排序、希尔排序、快速排序、堆排序是不稳定的排序方法。4从算法简单性比较直接插入排序、冒泡排序、直接选择排序都是简单的排序方法,算法简单,易于理解,而希尔排序、快速排序、堆排序、归并排序都是改进型的排序方法,算法比简单排