数据结构第7章.ppt
《数据结构第7章.ppt》由会员分享,可在线阅读,更多相关《数据结构第7章.ppt(135页珍藏版)》请在三一办公上搜索。
1、JYP,1,第7章 排序,数据元素之间的次序是一种重要的关系。本章学习最典型的排序算法,特别讨论内、外排序的不同策略。还介绍排序结果的顺序化方法。,JYP,2,7.1 引言,在数据结构中,数据元素之间的次序是一种重要的关系。排序的应用:查询结果需要按用户指定的属性排序。在已排序的数组中查找记录可最多用O(log2n)时间。核对两个已按学号排序的学生记录表可用O(m+n)时间完成。,JYP,3,记录 表示数据元素,具有一个或多个数据字段。关键字 用于区分记录的字段。表 记录的有限集合。给定一个记录表(R0,R1,Rn-1),设记录Ri的关键字值为Ki。关键字上存在一种次序关系 和一种相等关系=,
2、使得对于任意两个关键字值x和y,x=y,或x y,或y x。关系 满足传递性。用xy表示x=y或x y。,JYP,4,排序 确定一种置换p,使得记录表(Rp(0),Rp(1),Rp(n-1))的关键字满足性质Kp(0)Kp(1)Kp(n-1)。简单而言,排序就是按关键字非递减次序排列记录。如果在输入记录表中Ki=Kj(i j),且在排序后Ri在Rj之前,则称相应的排序方法是稳定的。否则就是不稳定的。根据整个排序过程能否在内存中完成,可将排序方法分为内排序和外排序。,JYP,5,记录的类定义:template class Element public:KeyType getKey()const
3、return key;/取记录的关键字值 void setKey(KeyType k)key=k;/修改记录的关键字/其它操作private:KeyType key;/关键字/其它字段;假设在KeyType被实例化时,相应实参类型的、=和=等已定义。,JYP,6,7.2 插入排序,插入排序的基本步骤:将记录R插入一个已排好序的记录序列R0,R1,Ri(K0K1Ki)中,使得新的有i+2个记录的序列也是排好序的。函数Insert实现了此步骤:template void insert(const Element e,Element*list,int i)/将e插入已排好序的 list0,listi
4、中,/使结果序列也排好序。list至少可容纳i+2个记录。while(i=0),JYP,7,插入排序从序列R0开始,连续插入记录R1,R2,Rn-1。n个记录可以通过n1次插入排序,如函数InsertionSort所示:template void InsertionSort(Element*list,const int n)for(int j=1;j n;j+)insert(listj,list,j 1);,JYP,8,分析:在最坏情况下,insert(e,list,i)在插入前要作i+1次比较。InsertionSort对i=j1=0,1,n2调用insert,其最坏情况时间复杂性是O()=
5、O(n2)插入排序的实际时间与输入表的相对无序状态有关。记录Ri是左出序的当且仅当Ki。,JYP,9,插入步骤中的循环只对左出序记录迭代。设k是左出序记录个数,则插入排序的时间是O(k+1)n)。当k=0,插入排序的时间为O(n)。这说明,当输入表中左出序记录很少时,插入排序的实际性能十分理想。,JYP,10,例7.1 设n=5,输入关键字为5,4,3,2,1,则每次插入后表中记录的排列如下:,这时插入排序的性能达到最坏情况。,JYP,11,例7.2 设n=5,输入关键字为2,3,4,5,1,则每次插入后表中记录的排列如下:,这时只有R4是左出序的。对于j=1,2,3,每次插入的时间只需O(1
6、);而对于j=4,插入时间为O(n)。,JYP,12,插入排序是稳定的。由于算法简单,对于较小的n(例如n20),插入排序几乎是最快的。插入排序的改进:1 二分插入排序 2 链表插入排序,JYP,13,7.3 希尔排序,希尔排序又称为减少增量排序。以插入排序为基础,并利用插入排序在最好情况下的性能,改善整个排序的性能。观察输入序列 8,7,6,5,1,2,3,4,其中左出序记录有7个。在插入排序中,首次交换8和7,只能减少1个左出序记录。如果首次交换8和1,则可减少2个左出序记录。,JYP,14,根据以上观察,可以跨越式地交换记录:将整个记录表划分为若干个子表,其相邻记录相隔incr个位置,i
7、ncr称为增量。利用插入排序对每个子表排序,以快速减少整个表中的左出序记录。每完成一次上述过程,增量减少,继续利用插入排序对按新增量划分的每个子表排序。最后一次增量必须为1,这等同于普通的插入排序,但由于前面的预处理,整个记录表已基本有序,此次插入排序有望快速完成。,JYP,15,例7.3 设n=8,输入序列为8,7,6,5,1,2,3,4,取incr=4,2,1。处理过程如下所示:,JYP,16,增量的一个较有效的选择是从 incr=n/3+1开始,按incr=incr/3+1减少增量。由于跨越式交换,希尔排序是不稳定的。为了反映增量,对InsertionSort及其调用的insert作少量
8、修改,得到用于希尔排序的InsertionSort2以及insert2。,JYP,17,template void insert2(const Element e,Element*list,int i,int incr)while(i=0),JYP,18,进一步可得希尔排序函数ShellSort:template void ShellSort(Element*list,int n)int incr=n;do/对每个增量 incr=incr/3+1;for(int start=0;start 1);,JYP,19,大规模实验研究表明:当记录个数n很大时,希尔排序对记录的移动次数在n1.25到1.
9、6n1.25的范围之内。这比插入排序有实质性的改进。,JYP,20,7.4 快速排序,插入排序将控制当前插入的基准记录Ri插入相对于已排好序的子表(R0,Ri1)的正确位置,而快速排序将基准记录Ri放在相对于整个记录表的正确位置。设输入表为(Rleft,Rright),如果Ri放在位置s(i),则有KjKs(i)(j s(i))。,JYP,21,因此,根据Ri的关键字大小,整个记录表被划分为左子表(Rleft,Rs(i)1)和右子表(Rs(i)+1,Rright),如下图所示:,由于左、右子表的记录分别在位置s(i)的左、右边,可独立地对这两个子表排序。,JYP,22,基准记录可任意选取,在此
10、选第一个记录Rleft。划分记录表:从左到右扫描,找到关键字大于等于Kleft的记录Ri;从右到左扫描,找到关键字小于等于Kleft的记录Rj;交换Ri和Rj;继续上述过程,直到i,j错位。,JYP,23,算法QuickSort给出了实现细节:template void QuickSort(Element*list,const int left,const int right)/排序listleft,listright,任选listleft为基准记/录,假设listleft.keylistright+1.key。if(left pivot);if(i j)InterChange(list,i,
11、j);while(i j);,JYP,24,InterChange(list,left,j);QuickSort(list,left,j 1);/排序左子表 QuickSort(list,j+1,right);/排序右子表 其中函数InterChange(list,a,b)交换记录lista和listb。调用QuickSort(list,0,n 1)可对表list的第0到第n 1个记录排序。为了简化边界判断,假设记录listn的关键字不小于其余关键字。,JYP,25,例7.4 设输入表记录的关键字为(26,5,37,1,61,11,59,15,48,19),每次调用QuickSort时记录表的
12、状态如下:,JYP,26,分析:用于划分记录表的时间是O(n),设下面的c都是常数。(1)用Tworst(n)表示快速排序的最坏时间复杂性。在最坏情况下,两个子表的长度分别为n1和0,因此 Tworst(n)cn+Tworst(n1)cn+c(n1)+Tworst(n2)c=cn(n+1)/2=O(n2),JYP,27,(2)用Topt(n)表示快速排序的最佳时间复杂性。在最佳情况下,两个子表的长度相同,每个子表的长度最多是n/2,因此 Topt(n)cn+2Topt(n/2)cn+2(cn/2+2Topt(n/4)2cn+4Topt(n/4)cn log2n+nTopt(1)=O(n log
13、 n),JYP,28,(3)根据下面的引理7.1,快速排序的平均时间复杂性也是O(n log n)。而且,实验结果表明,快速排序的平均性能最好。引理7.1:设Tavg(n)为快速排序的平均时间复杂性,则存在常数k,使得对于n2,Tavg(n)knlogen 成立。证明:在调用QuickSort(list,0,n 1)中,K0被放在位置j。两个子表的长度为j和n1j,相应的平均时间是Tavg(j)+Tavg(n1j)。由于j可按同等概率取值0到n1,因此,JYP,29,Tavg(n)cn+=cn+,n2(7.1)设Tavg(0)b和Tavg(1)b,b为常数,k=2(b+c)。对n应用归纳法可证
14、,Tavg(n)knlogen(n 2)。,JYP,30,当n=2,由(7.1)式可得:Tavg(2)2c+2b 2kloge2。假设对于2n m,Tavg(n)knlogen。则 Tavg(m)cm+=cm+cm+(7.2),JYP,31,由于jlogej是j的递增函数,由(7.2)式可得 Tavg(m)cm+=cm+=cm+=kmlogem+(cm+),JYP,32,当m2,cm+0,所以 Tavg(m)kmlogem,JYP,33,对基本快速排序算法的改进:基准记录的选择:选当前记录表的第一、中间和最后一个记录中关键字为中间值的记录。用排序小记录表较快的方法如插入排序代替快速排序。当需要
15、排序的记录表小于一定长度时,快速排序已使整个表接近于排好序,这时对整个记录表调用一次插入排序可使所有记录就序。,JYP,34,快速排序的最大递归深度是n。递归排序两个子表中较小的,再用循环代替第二个递归,可使递归深度最多是O(log n),如函数ImpQuickSort所示:template void ImpQuickSort(Element*list,const int left,const int right)/排序listleft,listright,任选listleft为/基准记录,设listleft.key listright+1.key while(left pivot);if(i
16、 j)InterChange(list,i,j);while(i j);,JYP,35,InterChange(list,left,j);if(right j=j left)/左子表较小 ImpQuickSort(list,left,j 1);left=j+1;else/右子表较小 ImpQuickSort(list,j+1,right);right=j 1;,JYP,36,设S(n)为ImpQuickSort所需的栈空间,每层递归调用需要栈空间为c,则 S(n)c+S(n/2)2c+S(n/4)clog2n+S(1)=O(log n)。不难看出,快速排序是不稳定排序。,JYP,37,7.5
17、归并排序,7.5.1 迭代归并排序 归并排序的基础是归并,即将两个已排序的表归并为一个已排序的表。在迭代归并排序中,需要归并的两个表:(initListl,initListm)(initListm+1,initListn)生成的结果表是(mergedListl,mergedListn)。,JYP,38,函数merge:template void merge(Element*initList,Element*mergedList,const int l,const int m,const int n)for(int i1=l,iResult=l,i2=m+1;i1=m,JYP,39,if(i1
18、m)for(int t=i2;t=n;t+)mergedListiResult+t-i2=initListt;else for(int t=i1;t=m;t+)mergedListiResult+t-i1=initListt;对merge的分析:for循环最多迭代nl+1次。循环之外的if语句总共最多移动nl+1个记录。因此,总时间是O(nl+1)。所需的辅助空间是O(nl+1)。,JYP,40,迭代归并排序的基本思想:将有n个记录的输入表解释为n个已排序表,每个表的长度为1。成对归并这些表,得到n/2个已排序表,每个表的长度为2(如果n是奇数,则最后一个表的长度为1)。再归并这n/2个表,如
19、此继续直到只剩下一个已排序的表。,JYP,41,例7.5 对输入表(26,5,77,61,11,59,15,48,19),的每一遍扫描中归并子表的过程:,JYP,42,可见,需要对输入表进行多次归并扫描。设扫描时输入表中已排序子表的长度为len(最后一个子表的长度可能小于len),则经过一次归并扫描后已排序子表的长度变为2len,如下所示:,JYP,43,用i作为扫描指针,指向需归并的两个子表的前一个的第一记录,并随着归并的完成向右移动。如果in2len,则至少还有两个长度都是len的子表需要归并,否则需要作特殊边界处理。,JYP,44,函数MergePass:template void Me
20、rgePass(Element*initList,Element*resultList,const int n,const int len)/一遍归并扫描。将表initList的相邻子表归并到表resultList for(int i=0;i=n 2len;i+=2len)merge(initList,resultList,i,i+len1,i+2len1);/剩下的记录数 2len if(i+len1 n1)/归并最后两个长短不一的子表 merge(initList,resultList,i,i+len1,n1);else for(int t=i;t=n1;t+)resultListt=in
21、itListt;/复制最后一个子表,JYP,45,函数MergeSort反复调用MergePass完成排序:template void MergeSort(Element*list,const int n)Element*tempList=new Element n;for(int l=1;l n;l*=2)/l是当前被归并子表的长度 MergePass(list,tempList,n,l);l*=2;MergePass(tempList,list,n,l);/最后一遍可能只是复制 delete tempList;,JYP,46,对MergeSort的分析:对输入表进行多次扫描。第1遍归并长度
22、为1的表,第2遍归并长度为2的表。第i遍扫描归并长度为2i-1的表。总共需要log2n次扫描。每遍扫描的时间为O(n)。归并排序的总时间为O(n log n)。不难看出,归并排序是稳定排序。,JYP,47,7.5.2 递归归并排序,递归归并排序的基本思想:将记录表划分成大致等长的左、右子表。递归排序这些子表。再归并已排序的子表,从而使整个记录表就序。,JYP,48,例7.6 对输入表(26,5,77,61,11,59,15,48,19)进行递归归并排序过程中的子表划分:,JYP,49,将子表从一个数组归并到另一个数组需要复制数据。采用链表表示可避免数据复制。假设每个元素至少有两个字段:link
23、和key,其结构定义如下:template class Element private:KeyType key;/关键字 int link;/其它字段public:Element()link=-1;/-1表示空指针;,JYP,50,假设:所有访问Element私有数据成员的的函数已声明为Element的友元。listi.key和listi.link分别是记录i的key和link字段的值(0in1)。初始时listi.link=1(0in1),即每个记录构成一个只含其本身的链表。,JYP,51,函数ListMerge(list,start1,start2)将两个由start1和start2指向的
24、按关键字非递减次序链接的链表归并为一个按关键字非递减次序链接的链表,并返回首记录指针:template int ListMerge(Element*list,const int start1,const int start2)/将分别由start1和start2指向的已排序链表归并为/一个已排序链表,并返回其首记录的下标。记录/之间通过整型下标链接。int iResult,iStart,i1,i2;if(liststart1.key=liststart2.key)/定位结果表的首记录 iStart=iResult=start1;i1=liststart1.link;i2=start2;,JYP
25、,52,else iStart=iResult=start2;i2=liststart2.link;i1=start1;while(i1-1,JYP,53,函数rMergeSort利用ListMerge实现递归归并排序,并返回已排好序链表的首记录指针:template int rMergeSort(Element*list,const int left,const int right)/将listleft,listright按key排序。返回已/排序链表的首记录下标。if(left=right)return left;/最多只含一个记录 int mid=(left+right)/2;/分别排序
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 数据结构

链接地址:https://www.31ppt.com/p-6364979.html