数据结构PPT教学课件第9章 排序.ppt
《数据结构PPT教学课件第9章 排序.ppt》由会员分享,可在线阅读,更多相关《数据结构PPT教学课件第9章 排序.ppt(107页珍藏版)》请在三一办公上搜索。
1、9.1 排序基本概念 9.2 插入排序 9.3 交换排序 9.4 选择排序 9.5 归并排序 9.6 基数排序 9.7 内部排序总结 9.8 有关排序算法的C语言源程序 9.9 多路归并用于外排序的简介,第9章 排序,返回主目录,第9章排序,9.1 排序基本概念 排序(sorting)又称分类,意指把一批杂乱无章的数据序列重新排列成有序序列。按待排序的记录的数量多少,排序过程中涉及的存储介质不同,排序方法分为两大类:内部排序和外部排序。内部排序是指待排序的记录存放在计算机内存之中;外部排序是指待排序的记录数量很大,以至于内存容纳不下而存放在外存储器之中,排序过程需要访问外存。排序的依据可以是记
2、录的主关键字,也可以是次关键字,甚至是若干数据项的组合。为了讨论方便,把排序所依据的数据项统称排序关键字,简称关键字。,假设含有n个记录的序列为R1,R2,Rn,其相应的关键字序列为K1,K2,Kn,所谓排序就是将记录按关键字非递减(或非递增)的顺序重新排列起来。在待排序的记录中若有多个相同的关键字,在用某种方法排序之后,这些关键字相同的记录相对先后次序不变,则称这种排序方法是稳定的;否则是不稳定的。本章所介绍的内部排序方法包括插入排序、交换排序、选择排序、归并排序和基数排序。前四类排序是通过比较关键字的大小决定记录先后次序,也称为比较排序。基数排序是不经关键字比较的排序方法。为了讨论方便,在
3、此把排序关键字假设为整型。记录的结构定义为:,struct node int key;/*排序关键字域*/int oth;/*其它域,根据需要自己设定*/,9.2 插入排序,9.2.1 直接插入排序 直接插入排序(straight insertion sort)是一种最简单的排序方法。它的基本操作是将一个记录插入到一个长度为m(假设)的有序表中,使之仍保持有序,从而得到一个新的长度为m1的有序表。算法思路:设有一组关键字K1,K2,Kn,排序开始就认为K1是一个有序序列;让K2插入上述表长为1的有序序列,使之成为一个表长为2的有序序列;然后让K3插入上述表长为2的有序序列,使之成为一个表长为3
4、的有序序列;依次类推,最后让Kn插入上述表长为n-1的有序序列,得一个表长为n的有序序列。,例9.1 设有一组关键字序列55,22,44,11,33,这里n=5,即有5个记录。如图 9.1 所示。请将其按由小到大的顺序排序在具体实现Ki 向前边插入时,有两种方法。一种方法是让Ki与K1,K2,顺序比较;另一种方法是Ki与 Ki-1,Ki-2 倒着比较。这里选用后一种方法。用一维数组r做存储结构,n表示记录个数,MAXSIZE为常量,且MAXSIZEn。则算法如下:算法 9.1void stinsort(struct node rMAXSIZE,int n)for(i=2;i=n;i+)/*共进
5、行n-1趟插入*/r0=ri;/*r0为监视哨,也可做下边循环结束标志*/,j=i-1;while(rj.key r0.key)rj+1=rj;j-;rj+1=r0;/*将r0即原ri记录内容,插到rj后一位置*/*stinsort*/此算法外循环n-1次,在一般情况下内循环平均比较次数的数量级为(n),所以算法总时间复杂度为(n2)。由于比较过程中,当Kj 与K0相等时并不移动记录,因此直接插入排序方法是稳定的。直接插入排序也可用单链表做存储结构。,当某结点i的关键字Kj与前边有序表比较时,显然先与K1 比较,再与K2比较,即从链表表头结点开始向后逐一比较更合适。另外,直接插入排序在原关键字
6、序列基本有序或n值较小时,它是一种最常用的排序方法,它的时间复杂度接近于O(n)。但是,当n值又较大时,此方法就不再适用。,9.2.2 折半插入排序 当直接插入排序进行到某一趟时,对于ri.key来讲,前面i-1个记录已经按关键字有序。此时不用直接插入排序的方法,而改为折半查找,找出ri.key应插的位置,然后插入。这种方法就是折半插入排序(binary insertion sort)。算法如下:算法 9.2 void binasort(struct node rMAXSIZE,int n)for(i=2;i=n;i+)ZK(r0=ri;l=1;h=i-1;,while(l=l;j-)rj+1
7、=rj;rl=r0;/*binasort*/,9.2.3 希尔排序 希尔排序(shell sort)是D希尔(D.L.Shell)提出的“缩小增量”的排序方法。它的作法不是每次一个元素挨一个元素的比较,而是初期选用大跨步(增量较大)间隔比较,使记录跳跃式接近它的排序位置;然后增量缩小;最后增量为 1,这样记录移动次数大大减少,提高了排序效率。希尔排序对增量序列的选择没有严格规定。算法思路:先取一个正整数d1(d1=1),即所有记录成为一个组为止。一般选d1约为n/2,d1为 d1/2,d3为d1/2,d1=1。,例92 有一组关键字76,81,60,22,98,33,12,79,将其按由小到大
8、的顺序排序。这里n=8,取d1=4,d2=2,d3=1,其排序过程如图9.2所示。算法实现见算法9.3。算法 9.3 void shellsort(struct node rMAXSIZE,int n)k=n/2;/*k值代表前文中的d值*/while(k=1)for(i=k+1;i=n;i+),r0=ri;j=i-k;while(rj.keyr0.key)ZK)/*shellsort*/,此算法外层循环是增量由n/2逐步缩小到的循环。for所构成的循环是针对某一特定的增量k,进行大跨步跳跃式的插入排序。例如k=2时,关键字分成二组,见图9.2的第2行,其中第1组是76,12,98,60,排序
9、后的结果为12,60,76,98,插入操作如下:i=3 K1=76有序,K3=12向前插;i=5 12,76有序,K5=98不移动;i=7 12,76,98有序,K7=60向前插;,第2组是33,22,81,79,排序后的结果为22,33,79,81,插入操作如下:HT5”SS i=4 K2=33 有序,K2=22向前插;i=6 22,33 有序,K6=81不移动;i=8 22,33,81有序,K8=79向前插;对整个文件来说,排序结果实际上为:12,22,60,33,76,79,98,81。当K=1时,此算法就等同于直接插入排序方法。由于前边大增量的处理,使关键字大体有序,因此最后一趟排序移
10、动的记录少,处理速度快。,希尔排序的分析是一个复杂的问题,因为它的时间是所选定的“增量序列”的函数,这涉及到数学上一些尚未解决的难题。到目前为止,没有人找到一种最好的增量序列。有人在大量实验基础上推导出,希尔排序的时间复杂度为O(n1.3)。如果对关键字序列6,7,51,2,52,8进行希尔排序,可以看出希尔排序是不稳定的。,9.3 交换排序,9.3.1 冒泡排序 冒泡排序(bubble sort)是一种人们熟知的、最简单的交换排序方法。在排序过程中,关键字较小的记录经过与其它记录的对比交换,像水中的气泡向上冒出一样,移到序列的首部,故称此方法为冒泡排序法。排序的算法思路是:(1)让j取n至2
11、,将rj.key与rj-1.key比较,如果rj.keyrj-1.key,则把记录rj与rj-1交换位置,否则不进行交换。,例9.3 HT有一组关键字44,55,22,33,99,11,66,77,这里n=8,对它们进行冒泡排序。排序过程如图9.3所示。图中凡画有弧线的,表示记录发生过交换。请看第4趟处理,关键字的两两比较过程中,并未发生记录交换。这表明关键字已经有序,因此不必要进行第5趟至第7趟处理。算法如算法9.4。,void bubblesort(struct node rMAXSIZE,int n)i=1;do tag=0;for(j=n;ji;j-)if(rj.keyrj-1.key
12、),x=rj;rj=ri;ri=x;tag=1;ZK)i+;while(tag=1 或者i=n,已进行了n-1趟处理。该算法的时间复杂度为O(n2)。但是,当原始关键字序列已有序时,只进行一趟比较就结束,此时时间复杂度为O(n)。,9.3.2 快速排序 快速排序由霍尔(Hoare)提出,它是一种对冒泡排序的改正。由于其排序速度快,故称快速排序(quick sort)。快速排序方法的实质是将一组关键字K1,K2,Kn 进行分区交换排序。其算法思路是:(1)以第一个关键字K1为控制字,将 K1,K2,Kn 分成两个子区,使左区所有关键字小于等于K1,右区所有关键字大于等于K1,最后控制字居两个子区
13、中间的适当位置。在子区内数据尚处于无序状态。(2)将右区首、尾指针(记录的下标号)保存入栈,对左区进行与第(1)步相类似的处理,又得到它的左子区和右子区,控制字居中。,(3)重复第(1)、(2)步,直到左区处理完毕。然后退栈对一个个子区进行相类似的处理,直到栈空。由以上三步可以看出:快速排序算法总框架是进行多趟的分区处理;而对某一特定子区,则应把它看成又是一个待排序的文件,控制字总是取子区中第一个记录的关键字。现在设计一个函数hoare,它仅对某一待排序文件进行左、右子区的划分,使控制字居中;再设计一个主体框架函数quicksort,在这里多次调用hoare函数以实现对整个文件的排序。例9.4
14、 HT设有一组关键字46,56,14,43,95,19,18,72,这里n=8。试用快速排序方法由小到大进行排序。1)分区处理函数hoare,思路:首先用两个指针i、j分别指向首、尾两个关键字,i=1,j=8。第一个关键字46作为控制字,该关键字所属的记录另存储在一个x变量中。从文件右端元素rj.key开始与控制字x.key相比较,当rj.key大于等于x.key时,rj不移动,修改j指针,j-,直到rj.keyx.key,把记录ri移到文件右边j所指向的位置;再到文件右边修改j指针,j-。重复上面的步骤,直到i=j,此处就是控制字记录x所在的位置。,至此将文件分成了左、右两个子区,其具体操作
15、见图9.4。算法如算法 9.5(假设某区段文件,指向第一个记录的指针为l,指向最后一个记录的指针为h)。算法 9.5 int hoare(struct node rMAXSIZE,int l,int h)i=1;j=h;x=ri;do while(i=x.key)j-;if(ij)ri=rj;i+;,while(ij)然后右子区l=i+1,h=n,入栈,对左子区令l=1,h=i-1,再次调用hoare,如此反复,直到全部文件记录处理完毕。图9.5中第1行表示对例9.4的数据进行过一次分区处理之后的结果,在此基础上经过多次调用hoare后,最后得出第5行的结果。,下面给出快速排序的递归算法和非递
16、归算法。非递归算法:算法 9.6void quicksort1(struct node rMAXSIZE,int n)/*int sn2;辅助栈s*/l=1;h=n;tag=1;top=0;do while(lh)i=hoare(r,l,h);top+;stop0=i+1;stop1=h;h=i-1;,else l=stop0;h=stop1;top-;while(tag=1);/*quicksort1*/递归算法:算法 9.7void quicksort2(struct node r,int l,int h)if(lh)i=hoare(r,l,h);/*划分两个区*/,quicksort2(
17、r,l,i-1);/*对左分区快速排序*/quicksort2(r,i+1,h);/*对右分区快速排序*/*quicksort2*/,在主程序调用非递归算法比较简单易懂。若要调用递归算法,因函数的形参不同,需做预处理。主程序的主要操作如下:调用递归函数 调用非递归函数 creat(r,n);creat(r,n);l=1;h=n;quicksort1(r,n);quicksort2(r,l,h);输出r;输出r;,3)快速排序算法空间时间及稳定性分析 快速排序的非递归算法引用了辅助栈,它的深度为log2n。假设每一次分区处理所得的两个子区长度相近,那么可入栈的子区长度分别为:n/21,n/22,
18、n/23,n/2k,又因为n/2k=1,所以k=log2n。分母中2的指数恰好反映出需要入栈的子区个数,它就是log2n,也即栈的深度。在最坏情况下,比如原文件关键字已经有序,每次分区处理仅能得到一个子区。可入的子区个数接近n,此时栈的最大深度为n。快速排序主体算法时间运算量约O(log2n),划分子区函数运算量约O(n),所以总的时间复杂度为O(n log2n),它显然优于冒泡排序O(n2)。可是算法的优势并不是绝对的,试分析,当原文件关键字有序时,快速排序时间复杂度是O(n2),这种情况下快速排序不快。,而这种情况的冒泡排序是O(n),反而很快。在原文件记录关键字无序时的多种排序方法中,快
19、速排序被认为是最好的一种排序方法。例9.5 试对6,7,51,2,52,8进行快速排序。排序过程简述如下:67512528初始状态 52 7 51 6 7 8 2 52 516 7 8 2 52 51 6 7 8 最后状态,9.4 选择排序,9.4.1 简单选择排序 简单选择排序(simple selection sort)也是直接选择排序。此方法在一些高级语言课程中做过介绍,是一种较为容易理解的方法。对于一组关键字K1,K2,Kn,将其由小到大进行简单排序的具体思路是:首先从K1,K2,Kn中选择最小值,假如它是Kz,则将Kz与K1对换;然后从K2,K3,Kn中选择最小值Kz,再将Kz与Kz
20、对换;如此进行选择和调换n-2趟。第(n-1)趟,从Kn-1、Kn中选择最小值Kz,将Kz与Kn-1对换,最后剩下的就是该序列中的最大值,一个由小到大的有序序列就这样形成的。该算法的时间复杂度为O(n2)。,由此可见,对于n个记录的关键字,需要处理n-1趟;而在每趟之中,又有一个内循环。图9.6是一个有5个关键字3,4,1,5,2的简单选择排序过程的示意图。假设用变量z记下较小值的下标,则算法如算法 9.8。算法 9.8 void sisort(struct node rMAXSIZE,int n)for(i=1;in;i+)z=i;for(j=i+1;j=n;j+),if(rj.keyrz.
21、key)z=j;x=ri;ri=rz;rz=x;/*sisort*/,9.4.2 堆排序 除了简单选择排序之外,还有树形选择排序(锦标赛排序)。1964年威洛姆斯(J.Willioms)提出了进一步改正的排序方法,即堆排序(heap sort)。堆是n个元素的有限序列k1,k2,kn,它当且仅当满足如下关系:ki=”即可。堆是一种数据元素之间的逻辑关系,常用向量做存储结构。对于第 6 章中介绍的满二叉树,当对它的结点由上而下,自左至右编号之后,编号为i的结点是编号为2i和2i+1结点的双亲。反过来讲,结点2i是结点i的左孩子,结点2i+1是结点i的右孩子。图9.7表示完全二叉树和它在向量中的存
22、储状态。结点编号对应向量中的下标号。用堆的概念分析向量中的数据,它显然满足(上小、底大)堆的关系。不难看出,满足堆的逻辑关系的一组数据,可画成二叉树的形状,并且它是一棵完全二叉树树形。因此,也可借助完全二叉树来描述堆的概念。若完全二叉树中任一非叶子结点的值小于等于(或大于等于)其左、右孩子结点的值,则从根结点开始按结点编号排列所得的结点序列就是一个堆。在图9.8中(a)、(c)是堆,(b)、(d)不是堆。,堆排序的思路是:把n个记录存于向量r之中,把它看成完全二叉树,此时关键字序列不一定满足堆的关系。堆排序大体分两步处理。(1)初建堆。从堆的定义出发,当i=1,2,n/2时应满足ki=k2i和
23、ki=k2i+1。所以先取i=n/2(它一定是第n个结点双亲的编号),将以i结点为根的子树调整为堆;然后令i=i-1,将以i结点为根的子树调整为堆。此时可能会反复调整某些结点,直到i=1为止,堆初步建成。(2)堆排序。首先输出堆顶元素(一般是最小值),让堆中最后一个元素上移到原堆顶位置,然后恢复堆。因为经过第一步输出堆顶元素的操作后,往往破坏了堆关系,所以要恢复堆;重复执行输出堆顶元素、堆尾元素上移和恢复堆的步骤,直到全部元素输出完为止。,例9.6 设有n个记录(n=8)的关键字是46,55,13,42,94,17,5,80,试对它们进行堆排序。第一步:初建堆。因为n=8,所以从i=4开始,见
24、图9.9。调整成为堆是一个较复杂的过程,当i值确定之后用kz记下ki的值,用kz分别与k2i和k2i+1比较,可理解为kz值与结点i的左、右孩子的关键字比较。如果一开始kz比k2和k2+1均小,则不进行任何调整。例如i=4时,k4k8(4280),就不用调整,见图9.9(a)。如果结点i的某一个孩子的关键字小于kz,则把这个孩子结点移上来。如果结点i的两个孩子的关键字都小于kz,那么将两个孩子中较小的一个调整上来。,如果结点i的两个孩子的关键字都小于kz,那么将两个孩子中较小的一个调整上来。在图9.9(c)中,i=1时,k2、k3都小于kz(42,546),则让k3(即5)移上去。此时需要让k
25、z与更下一层的左、右孩子的关键字进行比较,直到某一层的左、右孩子的关键字不小于kz,或左、右孩子不存在为止。此时将kz填入适当位置,使之成为堆。在图9.9(c)中,先把5调整上来,然后把13移到5原来的位置上,最后将kz(即46)填到13原来的位置上。第二步:堆排序。这是一个反复输出堆顶元素,将堆尾元素移至堆顶,再调整恢复堆的过程。恢复堆的过程与初建堆中i=1时所进行的操作完全相同。请注意:每输出一次堆顶元素,堆尾的逻辑位置退1,直到堆中剩下一个元素为止。排序过程如图 9.10所示。,堆排序算法实现:由上述可知,有一种操作过程(即调整恢复堆)要被多次反复调用,那就是当i值确定之后,以ki为比较
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 数据结构PPT教学课件第9章 排序 数据结构 PPT 教学 课件
链接地址:https://www.31ppt.com/p-2268145.html