1、第九章 内部排序,9.1 概述 9.2 插入排序9.2.1 直接插入排序9.2.2 其它插入排序9.2.3 希尔排序 9.3 快速排序 9.4 选择排序9.4.1 简单选择排序9.4.3 堆排序 9.5 归并排序 9.6 各种内部排序方法的比较讨论,9.1 概述,排序:将一个数据元素(或记录)的任意序列,重新排列成一个按关键字有序的序列。,有序表与无序表:一组记录按关键字的递增或递减顺序排列得到的结果被称之为有序表,相应地,把排序前的状态称为无序表。,内部排序和外部排序: 内部排序:如果排序过程全部在内存中进行,则称为内部排序 外部排序:如果排序过程需要不断地进行内存与外存之间的数据交换,则称
2、为外部排序,正序表与逆序表:如果有序表是按关键字升序排列的,则称为升序表或正序表,否则称为降序表或逆序表。不失普遍性,一般只讨论正序表。,内部排序的方法:,插入排序:直接插入排序、折半插入排序、希尔排序 交换排序:冒泡排序、快速排序 选择排序:简单选择排序、堆排序 归并排序:2-路归并排序 其它排序: 多关键字排序、基数排序,排序基本操作: 比较两个关键字大小 将记录从一个位置移动到另一个位置,排序算法的稳定性: 考虑有多个数据元素具有相同关键字的情况。 稳定:具有相同关键字的数据元素的相对位置关系在排序前后保持不变。 不稳定:具有相同关键字的数据元素的相对位置关系在排序前后发生了改变。,例:
3、,49 38 65 97 76 13 27,i=2 38 (38 49) 65 97 76 13 27,i=3 65 (38 49 65) 97 76 13 27,i=4 97 (38 49 65 97) 76 13 27,i=5 76 (38 49 65 76 97) 13 27,i=6 13 (13 38 49 65 76 97) 27,i=1 ( ),i=7 (13 38 49 65 76 97) 27,27,97,76,65,49,38,27,9.2 插入排序,9.2.1 直接插入排序,排序过程:整个排序过程为n-1趟插入,即先将序列中第1个记录看成是一个有序子序列,然后从第2个记录开
4、始,逐个进行插入,直至整个序列有序。,直接插入排序算法: void InsertSort(SqList ,若待排序记录按关键字从小到大排列(正序),则 关键字比较次数:,记录移动次数:,若待排序记录按关键字从大到小排列(逆序),则 关键字比较次数:,记录移动次数:,若待排序记录是随机的,取平均值,则 关键字比较次数约:,记录移动次数约:,时间复杂度:T(n)=O(n) 空间复杂度:S(n)=O(1),直接插入排序是一种稳定的排序方法。,算法评价:,折半插入排序:用折半查找方法确定插入位置。,例:,i=1 (30) 13 70 85 39 42 6 20,i=2 13 (13 30) 70 85
5、 39 42 6 20,i=7 6 (6 13 30 39 42 70 85 ) 20,i=8 20 (6 13 20 30 39 42 70 85 ),9.2.2 其它插入排序,时间复杂度:T(n)=O(n) 空间复杂度:S(n)=O(1),折半插入排序算法: Void BinsertSort(SqList ,9.2.3 希尔排序,希尔排序(Shell Sort)又称为“缩小增量排序”。排序过程:先取一个正整数d1n,把所有相隔d1的记录放一组,组内进行直接插入排序;然后取d2d1,重复上述分组和排序操作;直至di=1,即所有记录放进一个组中排序为止。,49,13,38,27,27,4,55
6、,38,65,48,97,55,76,4,希尔排序算法: void ShellInsert(SqList ,希尔排序特点,子序列的构成不是简单的“逐段分割”,而是将相隔某个增量的记录组成一个子序列。关键字较小的记录跳跃式前移,在进行最后一趟增量为1的插入排序时,序列已基本有序。 希尔排序的时间复杂度在O(nlog2n)和O(n2 )之间,大致为O(n 1.3)。,由于希尔排序对每个子序列单独比较,在比较时进行元素移动,有可能改变相同排序码元素的原始顺序,因此希尔排序是不稳定的。 增量序列取法: 无除1以外的公因子。 最后一个增量值必须为1。,9.3 快速排序,冒泡排序,排序过程:将第一个记录的
7、关键字与第二个记录的关键字进行比较,若为逆序r1.keyr2.key,则交换;然后比较第二个记录与第三个记录;依次类推,直至第n-1个记录和第n个记录比较为止第一趟冒泡排序,结果关键字最大的记录被放在最后。,对前n-1个记录进行第二趟冒泡排序,结果使关键字次大的记录被放在第n-1个位置。重复上述过程,直到“在一趟排序过程中没有进行过交换记录的操作”为止。,快速排序算法: int Partition(SqList ,void QSort(SqList ,例,38,49,76,97,13,97,27,97,30,97,13,76,76,76,27,30,13,65,27,65,30,65,13,1
8、3,49,49,30,49,27,38,27,38,30,38,排序后序列为:13 27 30 38 49 65 76 97,算法评价 最好情况(正序)时: 比较次数:n-1 移动次数:0 最坏情况(逆序)时: 比较次数:,移动次数:,空间复杂度:S(n)=O(1),时间复杂度:T(n)=O(n),快速排序,首先从j所指位置向前搜索第一个关键字小于x的记录,并和rp交换。再从i所指位置起向后搜索,找到第一个关键字大于x的记录,和rp交换。重复上述两步,直至i=j为止。,基本思想:通过一趟排序,将待排序记录分割成独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小,则可分别对这两部分记
9、录进行排序,以达到整个序列有序。,排序过程:,对rst中记录进行一趟快速排序,附设两个指针i和j,设枢轴记录rp=rs,x=rp.key。初始时令i=s,j=t。,再分别对两个子序列进行快速排序,直到每个子序列只含有一个记录为止。,完成一趟排序: ( 27 38 13) 49 (76 97 65 50),分别进行快速排序: ( 13) 27 (38) 49 (50 65) 76 (97),快速排序结束: 13 27 38 49 50 65 76 97,49,27,49,65,13,49,49,97,x=49,时间复杂度: 最好情况(每次总是选到中间值作枢轴):T(n)=O(nlog2n)。 最
10、坏情况(每次总是选到最小或最大元素作枢轴):T(n)=O(n)。,算法评价,9.4 选择排序,9.4.1 简单选择排序,首先通过n-1次关键字比较,从n个记录中找出关键字最小的记录,将它与第一个记录交换。 再通过n-2次比较,从剩余的n-1个记录中找出关键字次小的记录,将它与第二个记录交换。 重复上述操作,共进行n-1趟排序后,排序结束。,排序过程:,例:,初始: 49 38 65 97 76 13 27 ,i=1,13,49,13 27 38 49 65 76 97 ,排序结束: 13 27 38 49 65 76 97,二趟: 13 38 65 97 76 49 27 ,i=2,27,38
11、,简单排序算法: void SelectSort(SqList ,时间复杂度: T(n)=O(n),算法评价,比较次数:,记录移动次数,最好情况:0,最坏情况:3(n-1),或,( i=1,2,.n/2 ),例: (96,83,27,38,11,9),例: (13,38,27,50,76,65,49,97),可将堆序列看成完全二叉树,则堆顶 元素(完全二叉树的根)必为序列中 n个元素的最小值或最大值,9.4.3 堆排序,堆的定义:n个元素的序列(k1,k2,kn),当且仅当满足下列关系时,称之为堆。,堆排序:将无序序列建成一个堆,得到关键字最小(或最大)的记录;输出堆顶的最小(大)值后,使剩余
12、的n-1个元素重又建成一个堆,则可得到n个元素的次小值;重复执行,得到一个有序序列。,堆排序需解决的两个问题: 如何由一个无序序列建成一个堆? 如何在输出堆顶元素之后,调整剩余元素,使之成为一个新的堆? 第二个问题解决方法筛选。 方法:输出堆顶元素之后,以堆中最后一个元素替代之;然后将根结点值与左、右子树的根结点值进行比较,并与其中小者进行交换;重复上述操作,直至叶子结点,将得到新的堆,称这个从堆顶至叶子的调整过程为“筛选”。,堆排序算法: Typedef SqList Heaptype;/堆采用顺序表存储表示 void HeapAdjust(HeapType ,例:,例: 含8个元素的无序序
13、列(49,38,65,97,76,13,27,50),第一个问题的解决方法:从无序序列的第n/2个元素(即此无序序列对应的完全二叉树的最后一个非终端结点)起,至第一个元素止,进行反复筛选。,算法评价,堆排序的时间复杂度为:T(n)=O(nlog2n)。,堆排序是一种不稳定的排序方法。,9.5 归并排序,归并将两个或两个以上的有序表组合成一个新的有序表。,多路归并排序:将三个或三个以上有序子区间合并成一个有序子区间的排序,称为多路归并排序。常见的有三路归并排序、四路归并排序等,具体实现的方法与二路归并排序类似。,2-路归并排序,排序过程:设初始序列含有n个记录,则可看成n个有序的子序列,每个子序
14、列长度为1。 两两合并,得到n/21个长度为2或1的有序子序列。 再两两合并,如此重复,直至得到一个长度为n的有序序列为止。,归并排序算法: void Merge(RcdType SR,RcdType ,例:,初始关键字: 49 38 65 97 76 13 27,一趟归并后: 38 49 65 97 13 76 27,二趟归并后: 38 49 65 97 13 27 76,三趟归并后: 13 27 38 49 65 76 97,9.6 各种内部排序方法的比较讨论,从时间复杂度比较,从平均时间复杂度来考虑,直接插入排序、冒泡排序、直接选择排序是三种简单的排序方法,时间复杂度都为O(n2),而快
15、速排序、堆排序、二路归并排序的时间复杂度都为O(nlog2n),希尔排序的复杂度介于这两者之间。,若从最好的时间复杂度考虑,则直接插入排序和冒泡排序的时间复杂度最好,为O(n),其它的最好情形同平均情形相同。若从最坏的时间复杂度考虑,则快速排序的为O(n2),直接插入排序、冒泡排序、希尔排序同平均情形相同,但系数大约增加一倍,所以运行速度将降低一半,最坏情形对直接选择排序、堆排序和归并排序影响不大。,从空间复杂度比较 归并排序的空间复杂度最大,为O(n),快速排序的空间复杂度为O(log2n),其它排序的空间复杂度为O(1)。,从稳定性比较 直接插入排序、冒泡排序、归并排序是稳定的排序方法,而
16、直接选择排序、希尔排序、快速排序、堆排序是不稳定的排序方法。,从算法简单性比较 直接插入排序、冒泡排序、直接选择排序都是简单的排序方法,算法简单,易于理解,而希尔排序、快速排序、堆排序、归并排序都是改进型的排序方法,算法比简单排序要复杂得多,也难于理解。,各种内排序方法的选择,从时间复杂度选择 对元素个数较多的排序,可以选快速排序、堆排序、归并排序,元素个数较少时,可以选简单的排序方法。,从空间复杂度选择 尽量选空间复杂度为O(1)的排序方法,其次选空间复杂度为O(log2n)的快速排序方法,最后才选空间复杂度为O(n)的2-路归并的排序方法。,一般选择规则 (1)当待排序元素的个数n较大,排序码分布是随机,而对稳定性不做要求时,则采用快速排序为宜。 (2)当待排序元素的个数n大,内存空间允许,且要求排序稳定时,则采用2-路归并排序为宜。 (3)当待排序元素的个数n大,排序码分布可能会出现正序或逆序的情形,且对稳定性不做要求时,则采用堆排序或2-路归并排序为宜。 (4)当待排序元素的个数n小,元素基本有序或分布较随机,且要求稳定时,则采用直接插入排序为宜。 (5)当待排序元素的个数n小,对稳定性不做要求时,则采用直接选择排序为宜,若排序码不接近逆序,也可以采用直接插入排序。冒泡排序一般很少采用。,