1、第九章、内部排序,一、主要内容:1、概述 2、插入排序 3、快速排序 4、选择排序 5、归并排序 6、基数排序 7、各种内部排序方法的比较讨论,二、教学要求: 了解内部排序的概念; 了解归并排序、基数排序的算法; 掌握插入类排序的算法,直接插入排序、希尔排序; 掌握交换类排序的算法,冒泡排序、快速排序; 掌握选择类排序的算法,简单选择排序、树形选择类排序、堆排序; 掌握各种排序方法的特点,能够对各种排序算法进行评价,并能加以灵活应用;,目录,9.1、概述9.2、插入排序9.3、快速排序 9.4、选择排序9.5、归并排序9.6、基数排序 排序算法小结,9.1 概述,1. 什么是排序?将一组杂乱无
2、章的数据按一定的规律顺次排列起来。,存放在数据表中,按关键字排序,定义:设有记录序列: R1、R2 Rn 其相应的关键字序列为: K1、K2 Kn ; 若存在一种确定的关系: Kx = Ky = = Kz则将记录序列 R1、R2 Rn 排成按该关键字有序的序列: Rx、Ry Rz 的操作,称之为排序。,2. 排序的目的是什么?,3.排序算法的好坏如何衡量?时间效率排序速度(即排序所花费的全部比较次数) 空间效率占内存辅助空间的大小 稳定性若两个记录A和B的关键字值相等,但排序后A、B的先后次序保持不变,则称这种排序算法是稳定的。,便于查找!,4. 什么叫内部排序?什么叫外部排序?,若待排序记录
3、都在内存中,称为内部排序; 若待排序记录一部分在内存,一部分在外存,则称为外部排序。,注:外部排序时,要将数据分批调入内存来排序,中间结果还要及时放入外存,显然外部排序要复杂得多。 大多数排序算法都有两个基本的操作: (1)比较两个关键字的大小 (2)将记录从一个位置移动到另一个位置 记录序列的存储方式: (1)顺序存储 (2)静态链表 (3)地址,注:大多数排序算法都是针对顺序表结构的(便于直接移动元素),5. 顺序存储(顺序表)的抽象数据类型如何表示?,Typedef struct /定义每个记录(数据元素)的结构KeyType key ; /关键字 InfoType otherinfo;
4、 /其它数据项 RecordType ;,Typedef struct /定义顺序表的结构RecordType r MAXSIZE +1 ; /存储顺序表的向量/r0一般作哨兵或缓冲区int length ; /顺序表的长度 SqList ;,# define MAXSIZE 20 /设记录不超过20个 typedef int KeyType ; /设关键字为整型量(int型),6. 内部排序的算法有哪些?,按排序的规则不同,可分为5类: 插入排序 快速排序 选择排序 归并排序 基数排序,d关键字的位数(长度),按排序算法的时间复杂度不同,可分为3类: 简单的排序算法:时间效率低,O(n2)
5、先进的排序算法: 时间效率高,O( nlog2n ) 基数排序算算法:时间效率高,O( dn),9.2 插入排序,基本思想:,插入排序有多种具体实现算法:9.2.1 直接插入排序9.2.2 折半插入排序9.2.3 希尔排序,每步将一个待排序的对象,按其关键码大小,插入到前面已经排好序的一组对象的适当位置上,直到对象全部插入为止。,简言之,边插入边排序,保证子序列中随时都是排好序的。,9.2.1 直接插入排序,新元素插入到哪里?,例1:关键字序列T=(13,6,3,31,9,27,5,11),请写出直接插入排序的中间过程序列。,【13】, 6, 3, 31, 9, 27, 5, 11 【6, 1
6、3】, 3, 31, 9, 27, 5, 11 【3, 6, 13】, 31, 9, 27, 5, 11 【3, 6, 13,31】, 9, 27, 5, 11 【3, 6, 9, 13,31】, 27, 5, 11 【3, 6, 9, 13,27, 31】, 5, 11 【3, 5, 6, 9, 13,27, 31】, 11 【3, 5, 6, 9, 11,13,27, 31】,在已形成的有序表中线性查找,并在适当位置插入,把原来位置上的元素向后顺移。,例2:关键字序列T= (21,25,49,25*,16,08), 请写出直接插入排序的具体实现过程。,*表示后一个25,i=1,21,i=2
7、,i=3,i=5,i=4,i=6,25,25,25,49,49,49,25*,49,16,16,08,49,解:假设该序列已存入一维数组V7中,将V0作为缓冲或暂存单元(Temp)。则程序执行过程为:,初态:,16,25,21,16,完成!,直接插入排序的演示,void InsertSort(SqList /* 插入到正确位置 */,直接插入排序算法:,若设待排序的对象个数为n,则算法需要进行n-1次插入。 最好情况: 排序前对象已经按关键码大小从小到大有序,每趟只需与前面的有序对象序列的最后一个对象的关键码比较 1 次,移动 2 次对象。因此,总的关键码比较次数为n-1,对象移动次数为 2(
8、n-1)。,直接插入排序的算法分析,最坏情况: 第i趟插入时,第i个对象必须与前面i-1个对象都做关键码比较,并且每做 1 次比较就要做 1 次数据移动。则总的关键码比较次数KCN和对象移动次数RMN分别为,若待排序对象序列中出现各种可能排列的概率相同,则可取上述最好情况和最坏情况的平均情况。在平均情况下的关键码比较次数和对象移动次数约为 n2/4。因此,直接插入排序的时间复杂度为 o(n2)。 直接插入排序是一种稳定的排序方法。,9.2.2 折半插入排序,优点:比较的次数大大减少,全部元素比较次数仅为O(nlog2n)。时间效率:虽然比较次数大大减少,可惜移动次数并未减少,所以排序效率仍为O
9、(n2) 。空间效率: O(1) 稳定性:稳定,新元素插入到哪里?,在已形成的有序表中折半查找,并在适当位置插入,把原来位置上的元素向后顺移。,例: 将20用折半插入法插入到顺序表(6 13 30 39 42 70 85 ),(6 13 30 39 42 70 85 ) 20,i=5 20 (6 13 20 30 39 42 70 85 ),void BInsertSort(SqList /* 插入 */,Q:若记录是链表结构,用直接插入排序行否?折半插入排序呢?A:直接插入不仅可行,而且还无需移动元素,时间效率更高!,折半插入排序的改进2-路插入排序见教材P267。,但链表无法“折半”!,折
10、半插入排序的算法分析,折半查找比顺序查找快,所以折半插入排序就平均性能来说比直接插入排序要快。 在插入第 i 个对象时,需要经过 log2i +1 次关键码比较,才能确定它应插入的位置。因此,将 n 个对象用折半插入排序所进行的关键码比较次数为:n*log2n 折半插入排序是一个稳定的排序方法。,9.2.3 希尔(shell)排序(又称缩小增量排序),基本思想:先将整个待排记录序列分割成若干子序列,分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行一次直接插入排序。 技巧:子序列的构成不是简单地“逐段分割”,而是将相隔某个增量dk的记录组成一个子序列,让增量dk逐趟缩短(
11、例如依次取5,3,1),直到dk1为止。 优点:让关键字值小的元素能很快前移,且序列若基本有序时,再用直接插入排序处理,时间效率会高很多。,38,例:关键字序列 T=(49,38,65,97, 76, 13, 27, 49*,55, 04),请写出希尔排序的具体实现过程。,初态:,第1趟 (dk=5),第2趟 (dk=3),第3趟 (dk=1),49,13,13,49,38,27,65,49*,97,55,76,04,27,38,65,49*,97,55,13,55,76,04,55,13,27,04,27,04,49,49*,49,49*,76,38,76,65,65,97,97,13,27
12、,04,49*,76,97,算法分析:开始时dk 的值较大,子序列中的对象较少,排序速度较快;随着排序进展,dk 值逐渐变小,子序列中对象个数逐渐变多,由于前面工作的基础,大多数对象已基本有序,所以排序速度仍然很快。,ri,时间效率:,空间效率:O(1)因为仅占用1个缓冲单元 算法的稳定性:不稳定因为49*排序后却到了49的前面,希尔排序算法,O(n1.25)O(1.6n1.25)经验公式,void ShellSort(SqList ,void ShellInsert(SqList /* 插入 */,希尔排序演示,希尔排序算法分析,对特定的待排序对象序列,可以准确地估算关键码的比较次数和对象移
13、动次数。但想要弄清关键码比较次数和对象移动次数与增量选择之间的依赖关系,并给出完整的数学分析,还没有人能够做到。Knuth利用大量的实验统计资料得出,当n很大时,关键码平均比较次数和对象平均移动次数大约在 n1.25 到 1.6n1.25 的范围内。这是在利用直接插入排序作为子序列排序方法的情况下得到的。,9.3快速排序,两两比较待排序记录的关键码,如果发生逆序(即排列顺序与排序后的次序正好相反),则交换之,直到所有记录都排好序为止。,交换排序的主要算法有:1) 起泡排序2) 快速排序,基本思想:,1、 起泡排序,基本思路:每趟不断将记录两两比较,并按“前小后大”(或“前大后小”)规则交换。优
14、点:每趟结束时,不仅能挤出一个最大值到最后面位置,还能同时部分理顺其他元素;一旦下趟没有交换发生,还可以提前结束排序。前提:顺序存储结构,例:关键字序列 T=(21,25,49,25*,16,08),请写出冒泡排序的具体实现过程。,21,25,49, 25*,16, 08 21,25,25*,16, 08 , 49 21,25, 16, 08 ,25*,49 21,16, 08 ,25, 25*,49 16,08 ,21, 25, 25*,49 08,16, 21, 25, 25*,49,初态: 第1趟 第2趟 第3趟 第4趟 第5趟,排序过程 1)将第一个记录的关键字与第二个记录的关键字进行
15、比较,若为逆序r1.keyr2.key,则交换;然后比较第二个记录与第三个记录;依次类推,直至第n-1个记录和第n个记录比较为止第一趟冒泡排序,结果关键字最大的记录被安置在最后一个记录上 2)对前n-1个记录进行第二趟冒泡排序,结果使关键字次大的记录被安置在第n-1个记录位置 3)重复上述过程,直到“在一趟排序过程中没有进行过交换记录的操作”为止,void bubble_sort(SqList *L) int m,i,j,flag=1;RecordType x;m=n-1;while(m0)j+) if(L-rj.keyL-rj+1.key), flag=1;x=L-rj;L-rj=L-rj+
16、1;L-rj+1=x;m-; ,起泡排序演示,起泡排序的算法分析,时间效率:O(n2) 因为要考虑最坏情况 空间效率:O(1) 只在交换时用到一个缓冲单元 稳 定 性: 稳定 25和25*在排序前后的次序未改变,详细分析: 最好情况:初始排列已经有序,只执行一趟起泡,做 n-1 次关键码比较,不移动对象。 最坏情形:初始排列逆序,算法要执行n-1趟起泡,第i趟(1 i n) 做了n- i 次关键码比较,执行了n-i 次对象交换。此时的比较总次数KCN和记录移动次数RMN为:,2、 快速排序,从待排序列中任取一个元素 (例如取第一个) 作为中心,所有比它小的元素一律前放,所有比它大的元素一律后放
17、,形成左右两个子表;然后再对各子表重新选择中心元素并依此规则调整,直到每个子表的元素只剩一个。此时便为有序序列了。,基本思想:,优点:因为每趟可以确定不止一个元素的位置,而且呈指数增加,所以特别快! 前提:顺序存储结构,排序过程:对rst中记录进行一趟快速排序,附设两个指针i和j,设枢轴记录rp=rs,x=rp.key 初始时令i=s,j=t首先从j所指位置向前搜索第一个关键字小于x的记录,并和rp交换;再从i所指位置起向后搜索,找到第一个关键字大于x的记录,和rp交换; 重复上述两步,直至i=j为止。再分别对两个子序列进行快速排序,直到每个子序列只含有一个记录为止,( ),,例1:关键字序列
18、 T=(21,25,49,25*,16,08),请写出快速排序的算法步骤。,21, 25, 49, 25*,16, 08,初态: 第1趟: 第2趟: 第3趟:,08,16,21,25, 25*,(49),21,16,08,,( ),25,25*,49,(08),16,21,,25,(25*,49),1.这种不断划分子表的过程,计算机如何自动实现?,编程时: 每一趟的子表的形成是采用从两头向中间交替式逼近法; 由于每趟中对各子表的操作都相似,主程序可采用递归算法。,int Partition(SqList /以子表的首记录作为支点记录,放入r0单元 (续下页),一趟快速排序算法(针对一个子表的操
19、作),pivotkey=rlow.key; /取支点的关键码存入pivotkey变量,while(low =pivotkey ) - -high;rlow=rhigh; /将比支点小的记录交换到低端;while(lowhigh /将比支点大的记录交换到高端;,rlow=r0; /支点记录到位;return low; /返回支点记录所在位置。 /Partition,Low=high=3,本趟停止,将支点定位并返回位置信息,例2:关键字序列 T=(21,25,49,25*,16,08),请写出快速排序算法的一趟实现过程。,high,low,21,08,25,16,49,25*,3,21,pivot
20、key=21,08,25,16,49,( 08 ,16 ) 21 ( 25* , 49, 25 ),25*跑到了前面,不稳定!,void QSort ( SqList ,整个快速排序的递归算法:,/长度1,/对顺序表L中的子序列r lowhigh 作快速排序,/一趟快排,将r 一分为二,/在左子区间进行递归快排,直到长度为1,/在右子区间进行递归快排,直到长度为1,/QSort,新的low,void QuickSort ( SqList ,对顺序表L进行快速排序的操作函数为:,例3:以关键字序列(256,301,751,129,937,863,742,694,076,438)为例,写出执行快速
21、算法的各趟排序结束时,关键字序列的状态。,原始序列: 256,301,751,129,937,863,742,694,076,438,第1趟 第2趟 第3趟 第4趟,256,301,751,129,937,863,742,694,076,438,076,129,256,751,937,863,742,694,301,438,256,076,301,129,751,256,076,129,256,438,301,694,742,694,863,937,751,076,129,256,438,301,694,742,751,863,937,076,129,256,301,301,694,742,7
22、51,863,937,438,076,129,256,301,438,694,742,751,863,937,快速排序算法演示,快速排序算法详细分析:,快速排序是递归的,需要有一个栈存放每层递归调用时的指针和参数(新的low和high)。 可以证明,函数quicksort的平均计算时间也是O(nlog2n)。实验结果表明:就平均计算时间而言,快速排序是我们所讨论的所有内排序方法中最好的一个。 最大递归调用层次数与递归树的深度一致,理想情况为 log2(n+1) 。因此,要求存储开销为 o(log2n)。 如果每次划分对一个对象定位后,该对象的左侧子序列与右侧子序列的长度相同,则下一步将是对两个
23、长度减半的子序列进行排序,这是最理想的情况。此时,快速排序的趟数最少。,在最坏的情况,即待排序对象序列已经按其关键码从小到大排好序的情况下,其递归树成为单支树,每次划分只得到一个比上一次少一个对象的子序列。这样,必须经过 n-1 趟才能把所有对象定位,而且第 i 趟需要经过 n-i 次关键码比较才能找到第 i 个对象的安放位置,总的关键码比较次数将达到n2/2快速排序是一个不稳定的排序方法,讨论2. “快速排序”是否真的比任何排序算法都快?,设每个子表的支点都在中间(比较均衡),则: 第1趟比较,可以确定1个元素的位置; 第2趟比较(2个子表),可以再确定2个元素的位置; 第3趟比较(4个子表
24、),可以再确定4个元素的位置; 第4趟比较(8个子表),可以再确定8个元素的位置; 只需log2n 1趟便可排好序。,基本上是!因为每趟可以确定的数据元素是呈指数增加的!,而且,每趟需要比较和移动的元素也呈指数下降,加上编程时使用了交替逼近技巧,更进一步减少了移动次数,所以速度特别快。,9.4 选择排序,基本思想:在待排记录中依次选择关键字最小的记录作为有序序列的最后一条记录,逐渐缩小范围直至全部记录选择完毕。,1.简单选择排序,排序过程: 1)首先通过n-1次关键字比较,从n个记录中找出关键字最小的记录,将它与第 一个记录交换 2)再通过n-2次比较,从剩余的n-1个记录中找出关键字次小的记
25、录,将它与第二个记录交换 3)重复上述操作,共进行n-1趟排序后,排序结束,例,初始: 49 38 65 97 76 13 27 ,i=1,13,49,一趟: 13 38 65 97 76 49 27 ,i=2,27,38,六趟: 13 27 38 49 65 76 97 ,排序结束: 13 27 38 49 65 76 97,void SelectSort(SqList /SelectSort,特点:1) 算法简单, 时间复杂度为O(n2)2) 序列存储:顺序、链式3) 稳定,2.树型选择排序,-又成为锦标赛排序,排序思想:首先对n个记录的关键字进行两两比较,然后在n/2个 较小者之间再进行
26、两两比较,如此重复,直至找到最小关 键字的记录为止,49 38 65 97 76 13 27 49,例如:,13为最小关键字,27为次小关键字,3.堆排序 堆的定义,若有n个元素(a1,a2,a3,an),当满足如下条件:aia2i aia2i (1) aia2i+1 或 (2) aia2i+1 其中i=1,2,n/2则称此n个元素a1,a2,a3,an为一个堆。,若将此元素序列按顺序组成一棵完全二叉树,则(1)称为小根堆(二叉树的所有根结点值小于或等于左右孩子的值),(2)称为大根堆(二叉树的所有根结点值大于或等于左右孩子的值)。,小根堆,大根堆,不是堆,不是堆,堆与完全二叉树关系 若n个元
27、素a1,a2,a3,an满足堆,且让结点按1、2、3、n顺序编号,根据完全二叉树的性质(若i为根结点,则左孩子为2i,右孩子为2i+1)可知,一个堆对应着一颗完全二叉树,堆排序实际与一棵完全二叉树有关。若将元素初始序列组成一棵完全二叉树,则堆排序可以包含建立初始堆(使排序码变成能符合堆的定义的完全二叉树)和利用堆进行排序两个阶段。,实例 如序列(80,75,40,62,73,35,28,50,38,25,47,15)可以验证它满足堆的条件,因此是一个堆.下图给出其对应的完全二叉树.,1,堆排序:将无序序列建成一个堆,得到关键字最小(或最大)的记录;输出堆顶的最小(大)值后,使剩余的n-1个元素
28、重又建成一个堆,则可得到n个元素的次小值;重复执行,得到一个有序序列,这个过程叫 堆排序需解决的两个问题: 如何由一个无序序列建成一个堆? 如何在输出堆顶元素之后,调整剩余元素,使之成为一个新的堆? 第二个问题解决方法筛选 方法:输出堆顶元素之后,以堆中最后一个元素替代之;然后将根结点值与左、右子树的根结点值进行比较,并与其中小者进行交换;重复上述操作,直至叶子结点,将得到新的堆,称这个从堆顶至叶子的调整过程为“筛选”,例,堆排序演示,9.5 归并排序归并将两个或两个以上的有序表组合成一个新的有序表,叫2-路归并排序 排序过程 设初始序列含有n个记录,则可看成n个有序的子序列,每个子序列长度为
29、1 两两合并,得到n/2个长度为2或1的有序子序列 再两两合并,如此重复,直至得到一个长度为n的有序序列为止,例,初始关键字: 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 基数排序 1.多关键字排序 定义:,例 对52张扑克牌按以下次序排序: 23A23A 23A23A 两个关键字:花色( )面值(23A) 并且“花色”地位高于“面值”,多关键字排序方法最高位优先法(MSD):先对最高位关键字k1(如花色)排序,将序列分成
30、若干子序列,每个子序列有相同的k1值;然后让每个子序列对次关键字k2(如面值)排序,又分成若干更小的子序列;依次重复,直至就每个子序列对最低位关键字kd排序;最后将所有子序列依次连接在一起成为一个有序序列,最低位优先法(LSD):从最低位关键字kd起进行排序,然后再对高一位的关键字排序,依次重复,直至对最高位关键字k1排序后,便成为一个有序序MSD与LSD不同特点 按MSD排序,必须将序列逐层分割成若干子序列,然后对各子序列分别排序 按LSD排序,不必分成子序列,对每个关键字都是整个序列参加排序;并且可不通过关键字比较,而通过若干次分配与收集实现排序,2.链式基数排序 基数排序:借助“分配”和
31、“收集”对单逻辑关键字进行排序的一种方法 链式基数排序:用链表作存储结构的基数排序,链式基数排序步骤 设置10个队列,fi和ei分别为第i个队列的头指针和尾指针 第一趟分配对最低位关键字(个位)进行,改变记录的指针值,将链表中记录分配至10个链队列中,每个队列记录的关键字的个位相同 第一趟收集是改变所有非空队列的队尾记录的指针域,令其指向下一个非空队列的队头记录,重新将10个队列链成一个链表 重复上述两步,进行第二趟、第三趟分配和收集,分别对十位、百位进行,最后得到一个有序序列,例,基数排序演示,排序算法小结平均时间 最差 最佳 辅助空间 稳定性 直接插入 O(n2) O(n2) O(n) O(1) 稳定 起泡排序 O(n2) O(n2) O(n) O(1) 稳定 直接选择 O(n2) O(n2) O(n2) O(1) 不稳定 希尔排序 O(n1.5) O(1) 不稳定 快速排序 O(nlog2n) O(n2) 同平均 O(log2n) 不稳定 堆排序 O(nlog2n) 同平均 同平均 O(1) 不稳定 归并排序 O(nlog2n) 同平均 同平均 O(n) 稳定 基数排序 O(d(n+r) 同平均 同平均 O(n+r) 稳定,