西电初试专业课讲义-数据结构ch10sorting_第1页
西电初试专业课讲义-数据结构ch10sorting_第2页
西电初试专业课讲义-数据结构ch10sorting_第3页
西电初试专业课讲义-数据结构ch10sorting_第4页
西电初试专业课讲义-数据结构ch10sorting_第5页
免费预览已结束,剩余104页可下载查看

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

第十章

内部排序10.1排序的基本概念排序(sorting)将一个数据元素的任意序列重新排列成一个按关键字有序的序列。假设n个元素的序列{R1,R2,...,Rn},相应的关键字序列为{k1,k2,...,kn},确定1,2,...,n的一种排列p1,p2,...,pn

,使相应的关键字满足非递减(或非递增)关系kp1,kp2,...,kpn,从而得到一个按关键字有序的序列。这样的一个操作过程就是排序。稳定的排序方法若ki=kj,且在排序前的序列中Ri领先于Rj,排序后Ri仍领先于Rj,则称所用的排序方法是稳定的;反之,若可能使排序后的序列中Rj仍领先于Ri,则称所用的排序方法是不稳定的。内部排序和外部排序待排序的记录存放在计算机的内存中所进行的排序操作称为内部排序。待排序的记录数量很大,以致内存一次不能容纳全部记录,在排序过程中需要访问外存的排序过程称为外部排序。10.1排序的基本概念(续)正序与逆序若有序表是按排序码升序排列的,则称为升序表或正序表,否则称为降序表或逆序表。不失普遍性,我们一般只讨论正序表。排序方法度量排序过程主要是比较记录的关键字和移动记录。因此排序的时间复杂性可以算法执行中的数据比较次数及数据移动次数来衡量。当一种排序方法使排序过程在最坏或平均情况下所进行的比较和移动次数越少,则认为该方法的时间复杂性就越好。针对一种排序方法,不仅要分析它的时间复杂性,而且要分析它的空间复杂性、稳定性和简单性等。10.1排序的基本概念(续)内部排序外部排序

插入排序(直插排序、二分插入排序、希尔排序)

交换排序(冒泡排序、快速排序)

选择排序(直选排序、树型排序、堆排序)

归并排序(二路归并排序、多路归并排序)

分配排序(多关键字排序、基数排序)

多路平衡归并排序

置换-选择排序

最佳归并树排序将无序子序列中的一个或几个记录“插入”到有序序列中,从而增加记录的有序子序列的长度。通过“交换”无序序列中的记录从而得到其中关键字最小或最大的记录,并将它加入到有序子序列中,以此方法增加记录的有序子序列的长度。从记录的无序子序列中“选择”关键字最小或最大的记录,并将它加入到有序子序列中,以此方法增加记录的有序子序列的长度。通过“归并”两个或两个以上的记录有序子序列,逐步增加记录有序序列的长度。10.2插入排序1.直接插入排序直接插入排序(StraightInsertionSorting)的基本思想是:n个待排序的元素由一个有序表和一个无序表组成,开始时有序表中只包含一个元素。排序过程中,每次从无序表中取出第一个元素,将其插入到有序表中的适当位置,使有序表的长度不断加长,完成排序过程。有序序列R[1..i-1]R[i]无序序列R[i..n]有序序列R[1..i]无序序列R[i+1..n]10.2直接插入排序过程示例初始关键字序列3412492831525149*123456780监视哨i=21234492831525149*12i=31234492831525149*49i=41228344931525149*28i=51228313449525149*31i=61228313449525149*52i=71228313449515249*51i=8122831344949*515249*10.2直接插入排序算法数据结构定义#defineMAXSIZE20typedefintKeytype;//定义关键字类型为整型typedefstruct{KeyTypekey;//关键字项

InfoTypeotherinfo;//其他数据项}RedType;//记录类型typedefstruct{RedTyper[MAXSIZE+1];//r[0]闲置或用作哨兵

intlength;//顺序表长度}SqList;//顺序表类型10.2直接插入排序算法以顺序表作为存储结构的直接插入排序算法voidInsertSort(SqList&L){

for(i=2;i<=L.ength;i++)if(L.r[i].key<L.r[i-1].key){L.r[0]=L.r[i];L.r[i]=L.[i-1];for(j=i-2;L.r[0].key<L.r[j].key];j--)L.r[j+1]=L.r[j];L.r[j+1]=L.r[0];}//if}//InsertSort分析直接插入排序算法中关键字的比较次数和记录移动次数for(i=2;i<=L.length;i++)

if(L.r[i].key<L.r[i-1].key){L.r[0]=L.r[i];L.r[i]=L.[i-1];for(j=i-2;L.r[0].key<L.r[j].key];j--)L.r[j+1]=L.r[j];L.r[j+1]=L.r[0];}//ifif(L.r[i].key<L.r[i-1].key){L.r[0]=L.r[i];L.r[i]=L.r[i-1];for(j=i-2;L.r[0].key<L.r[j].key;j--)L.r[j+1]=L.r[j];L.r[j+1]=L.r[0];}//if最好情况下(正序)元素的比较次数为:n-1元素的移动次数为:0最坏情况下(逆序)元素的比较次数:(n+2)(n-1)/2元素的移动次数为:

(n+4)(n-1)/210.2直接插入排序算法以顺序表作为存储结构的直接插入排序算法时间复杂度:O(n2)在最好情况下(正序),元素的移动次数为0,比较次数为n–1;在最坏情况下(逆序),元素的移动次数为(n+4)(n-1)/2,比较次数为(n+2)(n-1)/2空间复杂度:O(1)只需要

1个辅助单元稳定的排序方法适用情况元素数目少,或者元素的初始序列基本有序10.2其他插入排序2.其他插入排序在寻找插入位置时采用二分查找,则称为折半插入排序;2-路插入排序在此基础上增加了辅助空间、减少了记录的移动;表插入排序就是通过链接指针,按关键码的大小,实现从小到大的链接过程,不移动元素,用静态链表实现。voidInsertSort(SqList&L)/*对顺序表L作折半插入排序*/{ for(i=2;i<=L.length;i++){L.r[0]=L.r[i];/*保存待插入元素*/

low=1;high=i-1;/*设置初始区间*/while(low<=high)/*确定插入位置*/{mid=(low+high)/2;

if(L.r[0].key>L.r[mid].key)low=mid+1;/*插入位置在高半区中*/elsehigh=mid-1;/*插入位置在低半区中*/}/*while*/for(j=i-1;j>=high+1;j--)/*high+1为插入位置*/L.r[j+1]=L.r[j];/*后移元素,留出插入空位*/L.r[high+1]=L.r[0];/*将元素插入*/}/*for*/}/*InsertSort*/10.2希尔排序3.希尔排序(Shell’sSort)也称为缩小增量排序,其改进原理主要基于以下两点元素基本有序时,直接插入排序的时间复杂度接近于O(n)元素数目n较少时,直接插入排序的效率较高希尔排序的算法思想先将整个待排序元素序列分割成若干个子序列(由相隔某个“增量”的元素组成的),分别进行直接插入排序,待整个序列中的元素基本有序(增量足够小)时,再对全体元素进行一次直接插入排序。10.2希尔排序过程示例初始关键字序列:4938659776132749*123456785504910增量d=5132749*5504493865123456789776910第一趟排序结果:增量d=3第二趟排序结果:130449*3827495565123456789776910增量d=1第三趟排序结果:0413273849*495565123456787697910132749*5504493865977610.2希尔排序算法voidShellInsert(SqList&L,intdk){//对顺序表L作一趟希尔排序

for(i=2;i<=L.ength;i++)if(L.r[i].key<L.r[i-1].key){L.r[0]=L.r[i];L.r[i]=L.[i-1];for(j=i-2;L.r[0].key<L.r[j].key];j--)L.r[j+1]=L.r[j];L.r[j+1]=L.r[0];}//if}//ShellInsertSortfor(i=dk+1i<=L.ength;i++)

if(L.r[i].key<L.r[i-1].key){L.r[0]=L.r[i];L.r[i]=L.[i-1];for(j=i-2;L.r[0].key<L.r[j].key];j--)L.r[j+1]=L.r[j];L.r[j+1]=L.r[0];}//ifif(L.r[i].key<L.r[i-dk].key){//需将L.r[i]插入有序增量子表

L.r[0]=L.r[i];//L.r[i]暂存入L.r[0]for(j=i-dk;j>0&&L.r[0].key<L.r[j].key;j-=dk)L.r[j+dk]=L.r[j];//寻找插入位置时记录后移

L.r[j+dk]=L.r[0];//插入

}//if10.2希尔排序算法(续)及分析voidShellInsert(SqList&L,intdk){//对顺序表L作一趟希尔排序L......}//ShellInsertSortvoidShellSort(SqList&L,intdlta[],intt){//按增量序列dlta[0..t-1]进行希尔排序

for(k=0;k<t;k++)ShellInsert(L,dlta[k]);//一趟增量为dlta[k]的希尔排序}//ShellInsertSort希尔排序的分析是一个复杂问题,增量序列的设置是关键,尚没有正式的依据说明如何设置最佳的增量序列,大量的实验表明希尔排序所需的比较和移动次数可达到n1.3希尔排序的空间复杂度为O(1),是不稳定的排序方法10.3交换排序1.起泡排序起泡排序(BubbleSorting)的基本思想是:将相邻位置的关键字进行比较,若为逆序则交换之。无序序列R[1..n-i+1]有序序列R[n-i+2..n]n-i+1无序序列R[1..n-i]有序序列R[n-i+1..n]比较相邻记录,将关键字最大的记录交换到n-i+1的位置上第i趟起泡排序若在一趟排序过程中没有进行过交换记录的操作,则整个排序过程终止。10.3起泡排序过程示例初始关键字序列:4938659776132749*1234567838496576132749*97第一趟排序后:384965132749*7697第二趟排序后:3849132749*657697第三趟排序后:3813274949*657697第四趟排序后:1327384949*657697第五趟排序后:1327384949*657697第六趟排序后:10.3起泡排序算法以顺序表作为存储结构的起泡排序算法voidBubbleSort(SqList&L){

for(i=2;i<=L.ength;i++)if(L.r[i].key<L.r[i-1].key){L.r[0]=L.r[i];L.r[i]=L.[i-1];for(j=i-2;L.r[0].key<L.r[j].key];j--)L.r[j+1]=L.r[j];L.r[j+1]=L.r[0];}//if}//BubbleSort分析起泡排序算法中关键字的比较次数和记录移动次数for(i=1,change=TRUE;i<L.length&&change;++i){

if(L.r[i].key<L.r[i-1].key){L.r[0]=L.r[i];L.r[i]=L.[i-1];for(j=i-2;L.r[0].key<L.r[j].key];j--)L.r[j+1]=L.r[j];L.r[j+1]=L.r[0];

}//for}//ifchange=FALSE;for(j=1;j<L.length-i+1;++j)if(L.r[j].key>L.r[j+1].key){L.r[0]=L.r[j];L.r[j]=L.r[j+1];L.r[j+1]=L.r[0];change=TRUE;}//if最好情况下,元素的比较次数为:n-1最坏情况下,比较次数为:n(n-1)/2最好情况下,元素的移动次数为:0最坏情况下,交换次数为:n(n-1)/210.3起泡排序算法以顺序表作为存储结构的起泡排序算法时间复杂度:O(n2)在最好情况下(正序),元素的交换次数为0,比较次数为n–1;在最坏情况下(逆序),元素的交换次数为n(n-1)/2,比较次数为(n+2)(n-1)/2空间复杂度:O(1)只需要

1个辅助单元进行交换稳定的排序方法适用情况元素数目少,或者元素的初始序列基本有序10.3交换排序(续)2.快速排序快速排序(QuickSorting)是迄今为止所有内排序算法中速度最快的一种。其基本思想是:取待排序序列中的某个元素作为基准(一般取第一个元素),通过一趟排序,将待排元素分为左右两个子序列,左子序列元素的关键字均小于或等于基准元素的关键字,右子序列的关键字则大于基准元素的关键字,然后分别对两个子序列继续进行排序,直至整个序列有序。无序的记录序列无序记录子序列(1)无序子序列(2)枢轴一次划分分别进行快速排序386597132749*551234567849049pivotij快速排序中的一趟划分快速排序中的一趟划分(续)386597132749*55123456784904949ijL.r[j]与pivot比较,L.r[j]小则与L.r[i]交换;否则j减10449L.r[j]与L.r[i]交换,i加1快速排序中的一趟划分(续)386597132749*55123456780449949pivotijL.r[i]与pivot比较,L.r[i]大则与L.r[j]交换;否则i增1快速排序中的一趟划分(续)L.r[i]与pivot比较,L.r[i]大则与L.r[j]交换;否则i增1386597132749*55123456780449949pivotij4965L.r[i]与L.r[j]交换,j减1快速排序中的一趟划分(续)384997132749*55123456780465949ijL.r[j]与pivot比较,L.r[j]小则与L.r[i]交换;否则j减1快速排序中的一趟划分(续)384997132749*55123456780465949pivotijL.r[j]与pivot比较,L.r[j]小则与L.r[i]交换;否则j减1快速排序中的一趟划分(续)L.r[j]与pivot比较,L.r[j]小则与L.r[i]交换;否则j减1384997132749*55123456780465949pivotij2749L.r[j]小则与L.r[i]交换,i加1快速排序中的一趟划分(续)pivot382797134949*55123456780465949pivotijL.r[i]与pivot比较,L.r[i]大则与L.r[j]交换;否则i增1L.r[i]大则与L.r[j]交换,j减14997快速排序中的一趟划分(续)pivotL.r[j]与pivot比较,L.r[j]小则与L.r[i]交换;否则j减1382749139749*55123456780465949pivotijL.r[j]小则与L.r[i]交换,i加11349快速排序中的一趟划分(续)382713499749*55123456780465949pivotij当i与j相等时,枢轴元素确定了位置i,结束本次划分

快速排序是对冒泡排序的一种改进方法,算法中元素的比较和交换是从两端向中间进行的,排序码较大的元素一次就能够交换到后面的单元,排序码较小的记录一次就能够交换到前面的单元,记录每次移动的距离较远,因而总的比较和移动次数较少。386597132749*551234567849049pivot快速排序中的一趟划分算法382713499749*551234567804659一次划分intPartition(SqList&L,intlow,inthigh){pivotkey=L.r[low].key;i=low;j=high;while(i<j){

while(i<j&&L.r[j].key>=pivotkey)j--;/*从后往前寻找比枢轴元素小者*/L.r[i]←→L.r[j];/*比枢轴元素小者交换到前半区间*/

while(i<j&&L.r[i].key<=pivotkey)i++;/*从前往后寻找比枢轴元素大者*/L.r[j]←→L.r[i];/*比枢轴元素大者交换到后半区间*/}returni;/*返回枢轴元素的位置*/}//Partition将交换改为元素的移动划分算法改进386597132749*55123456784904949ij0449386597132749*55123456784904949ij04386597132749551234567849049pivotij快速排序中的一趟划分快速排序中的一趟划分386597132749551234567804949pivotija[j]与pivot比较,a[j]小则a[j]→a[i]快速排序中的一趟划分386597132749551234567804949pivotij快速排序中的一趟划分386597132749551234567804949pivotija[i]与pivot比较,a[i]大则a[i]→a[j];否则i增1快速排序中的一趟划分386597132749551234567804949pivotija[i]与pivot比较,a[i]大则a[i]→a[j];否则i增1快速排序中的一趟划分389713274955123456780465949pivotij快速排序中的一趟划分389713274955123456780465949pivotija[j]与pivot比较,a[j]小则a[j]→a[i];

否则,利用j向前扫描快速排序中的一趟划分389713274955123456780465949pivotija[j]与pivot比较,a[j]小则a[j]→a[i];

否则,利用j向前扫描快速排序中的一趟划分389713274955123456780465949pivotija[j]与pivot比较,a[j]小则a[j]→a[i];

否则,利用j向前扫描快速排序中的一趟划分382797134955123456780465949pivotija[i]与pivot比较,a[i]大则a[i]→a[j];

否则,利用i向后扫描快速排序中的一趟划分382797134955123456780465949pivotij利用i向后扫描a[i]与pivot比较,a[i]大则a[i]→a[j];快速排序中的一趟划分382713974955123456780465949pivotij利用j向前扫描快速排序中的一趟划分382713974955123456780465949pivotija[j]与pivot比较,a[j]小则a[j]→a[i];

否则,利用j向前扫描快速排序中的一趟划分382713974955123456780465949pivotija[i]与pivot比较,a[i]大则a[i]→a[j];

否则,利用i向后扫描快速排序中的一趟划分382713974955123456780465949pivotij快速排序中的一趟划分382713974955123456780465949pivotiji与j相等时,完成一次划分快速排序中的一趟划分382713499749551234567804659intPartition(SqList&L,intlow,inthigh){L.r[0]=L.r[low];pivotkey=L.r[0].key;i=low;j=high;while(i<j){

while(i<j&&L.r[j].key>=pivotkey)j--;L.r[i]=L.r[j];

while(i<j&&L.r[i].key<=pivotkey)i++;L.r[j]=L.r[i];}L.r[i]=L.r[0];returni;}//Partition快速排序intPartition(SqList&L,intlow,inthigh){......returni;//返回枢轴元素的位置}//PartitionvoidQSort(SqList&L,intlow,inthigh){//对L.r[low]~L.r[high]的元素进行快速排序

if(low<high){pivotloc=Partition(L,low,high);//一趟划分

QSort(L,low,pivotloc-1);QSort(L,pivotloc+1,high);}//if}//QSort快速排序过程分析386597132749*551234567849049划分382713499749*550465划分38271304划分9749*5565划分132738划分2713划分49*5565划分6555276510.3快速排序分析以顺序表作为存储结构的快速排序算法时间复杂度:O(nlogn)快速排序在最好情形下(左、右子区间的长度大致相等),则结点数n与二叉树深度h应满足log2n<h<log2n+1,所以总的比较次数不会超过(n+1)log2n。因此,快速排序的最好时间复杂度应为O(nlog2n)。理论上已经证明,快速排序的平均时间复杂度也为O(nlog2n)。在最坏情况下(逆序或正序),时间复杂度为O(n2)空间复杂度:O(logn)在最坏情况下(逆序或正序),空间复杂度为O(n)不稳定的排序方法快速排序不适合对小规模的序列进行排序10.3快速排序分析以顺序表作为存储结构的快速排序算法时间复杂度:O(nlogn)快速排序在最好情形下的时间复杂度为O(nlog2n)。理论上已经证明,快速排序的平均时间复杂度也为O(nlog2n)。在最坏情况下(逆序或正序),时间复杂度为O(n2)空间复杂度:O(logn)在最坏情况下(逆序或正序),空间复杂度为O(n)不稳定的排序方法快速排序不适合对小规模的序列进行排序假设一次划分所得枢轴位置i=k,则对n个记录进行快排所需时间为:

T(n)=Tpass(n)+T(k-1)+T(n-k)其中Tpass(n)为对n个记录进行一次划分所需时间。若待排序列中记录的关键字随机分布,则k

取1至n

中任一值的可能性相同,由此可得快速排序所需时间的平均值为:10.3快速排序方法的改进枢轴元素的选取三者取中随机选择划分的过程中进行“起泡”操作当划分出的子序列长度小于某个值时,不再递归,而进行直接插入排序快速排序算法被认为是内部排序方法中最好的一种选择排序1327384949*55650497123456789最坏情况下的快速排序1327384949*556504971327384949*55659727384949*556597384949*5565974949*55659749*556597556597659797return最好情况下的快速排序046597135549*381234567849279划分043813495549*972765划分13043827划分6549*5597划分380413划分49*976565return划分0410.4选择排序1.简单选择排序简单选择排序的基本思想是:第一趟在n个记录中选取最小记录作为有序序列的第一个记录,第二趟在n-1个记录中选取最小记录作为有序序列的第二个记录,第i趟在n-i+1个记录中选取最小的记录作为有序序列多中的第i个记录。10.4简单选择排序过程示例初始关键字序列3412492831525149*123456780i=11234492831525149*i=21228493431525149*i=31228313449525149*i=41228313449525149*i=51228313449525149*i=6122831344949*5152i=7122831344949*5152return10.4简单选择排序算法以顺序表作为存储结构的简单选择排序算法voidSelectSort(SqList&L){//对顺序表作简单选择排序

for(i=1;i<L.ength;i++){for(k=i,j=i;k<=L.length;k++)if(L.r[k].key<L.r[j].key)j=k;if(j!=i){L.r[i]←

→L.r[j];}}//for}//SelectSort分析简单选择排序算法中关键字的比较次数和记录移动次数for(i=1;i<L.length;i++){

for(k=i,j=i;k<=L.length;k++)if(L.r[k].key<L.r[j].key)j=k;if(j!=i){L.r[i]←

→L.r[j];}}//for}//iffor(k=i+1,j=i;k<=L.length;k++)if(L.r[k].key<L.r[j].key)j=k;if(j!=i){L.r[i]←

→L.r[j];}在逆序情况下元素的比较次数:n(n-1)/2元素的移动次数为:3(n-1)/2在正序情况下元素的比较次数:n(n-1)/2元素的移动次数为:010.4简单选择排序算法分析以顺序表作为存储结构的简单选择排序算法时间复杂度:O(n2)在n个关键字中选出最小者,需要n-1次比较,继续在剩余的n-1个元素中选出次小者需要n-2次比较,依此类推。空间复杂度:O(1)只需要

1个辅助单元进行交换不稳定的排序方法适用情况元素数目少的情况无需完全排序的情况,比如,选出第i小的元素对简单选择排序过程进行改进:利用前面已经进行过的比较结果10.4选择排序2.树形选择排序(TreeSelectionSort)又称锦标赛排序(TournamentSort):首先对n个记录的关键字两两进行比较,然后在n/2个较小者之间再进行两两比较,如此重复,直至选出最小关键字的记录。整个过程可用一个含有n个叶结点的二叉树表示。例如3412492831525149*12283149*1231123412

492831525149*10.4选择排序2.树形选择排序(TreeSelectionSort)又称锦标赛排序(TournamentSort):首先对n个记录的关键字两两进行比较,然后在n/2个较小者之间再进行两两比较,如此重复,直至选出最小关键字的记录。选出最小记录后,将树中的该最小记录修改为∞,然后从该叶子结点所在子树开始,修改到达树根的路径上的结点34∞

492831525149*34283149*28312834∞

492831525149*10.4选择排序2.树形选择排序(TreeSelectionSort)又称锦标赛排序(TournamentSort):首先对n个记录的关键字两两进行比较,然后在n/2个较小者之间再进行两两比较,如此重复,直至选出最小关键字的记录。选出最小记录后,将树中的该最小记录修改为∞,然后从该叶子结点所在子树开始修改到达树根的路径上的结点以后每选出一个小元素,都只需进行(logn)次比较34∞

49∞31525149*34493149*34313134∞

492831525149*10.4选择排序2.树形选择排序的缺陷需要较多的辅助空间存在与“∞”进行比较的冗余比较34∞

49∞31525149*34493149*34313110.4选择排序3.堆排序(HeapSort)只需要一个元素的辅助空间算法的时间复杂度为O(nlogn)堆的定义对于n个元素的序列{k1,k2,...,kn},当且仅当满足以下关系时,称之为堆。

或10.4堆排序堆(完全二叉树)96833811279(a)大顶堆(maxheap)123685472430(b)小顶堆(minheap)539110.4堆排序3.堆排序(HeapSort)对一组待排序记录的关键字,首先把它们按堆的定义建成小(大)顶堆,然后输出堆顶的最小(大)关键字所代表的记录,再对剩余的关键字建堆,以便得到次小(大)的关键字,如此反复进行,直到全部关键字排成有序序列为止。

实现堆排序需要解决两个问题:

(1)如何建堆?

(2)输出堆顶元素后,如何将剩余元素重新调整为一个新堆?10.4堆排序过程示例下面是一个小顶堆,输出堆顶元素后,将剩余元素重新调整为一个新堆的方法是:从树根开始,若以某结点为根的子树不是堆,则将其孩子中的较小者与之交换,即将非堆的子树推向叶子方向12368547243053919136854724305312交换堆顶元素与序列末端的元素比较比较-交换return10.4堆排序过程示例(续)输出堆顶元素后,将剩余元素重新调整为一个新堆的方法是:从树根开始,若以某结点为根的子树不是堆,则将其孩子中的较小者与之交换,即将非堆的子树推向叶子方向2436854730915312继续向叶子方向调整2436854791305312比较比较交换10.4堆排序过程示例(续)一旦已调整为堆,则输出堆顶元素,重复将剩余元素重新调整为一个新堆。24368547309153125336854730912412交换堆顶元素与序列末端的元素10.4堆排序过程示例(续)输出堆顶元素后,将剩余元素重新调整为一个新堆3036854753912412调整成堆533685473091241210.4堆排序过程分析输出堆顶元素后,将剩余元素重新调整为一个新堆9136854753302412反复将堆顶元素与序列末端的元素的交换,并重新调整成堆。9185473653302412调整堆元素(筛选)

对于给出的关键字序列,经初始建堆后便得到小(大)顶堆,从而得到最小(大)关键字。

在输出堆顶元素之后,用堆中最后一个元素替代之。此时由于以K2和K3为根的子树仍具有堆的特性,因此只需自上而下进行调整即可。调整过程为:首先令K1的两个子树根进行比较,令其中较小(大)者与K1相比较,将最小(大)元素交换至K1,使得K1、K2和K3成为堆。由于交换后破坏了子树的堆性质,则需再次进行与上述过程类似的调整,直至进行到最下层的叶结点为止。调整后即得到一个有n-1个元素的新堆,从而得到次小关键字。10.4堆排序过程分析(续)输出堆顶元素后,将剩余元素重新调整为一个新堆调整堆元素(筛选)

对于给出的关键字序列,经初始建堆后便得到小(大)顶堆,从而得到最小(大)关键字。在输出堆顶元素之后,用堆中最后一个元素替代之。此时由于以K2和K3为根的子树仍具有堆的特性,因此只需自上而下进行调整即可。首先令K1将的两个子树根进行比较,令其中较小(大)者与K1相比较,将最小(大)元素交换至K1,使得K1、K2和K3成为堆。由于交换后破坏了右子树的堆,则需再次进行与上述类似的调整,直至进行到最下层的叶结点。调整后即得到一个有n-1个元素的新堆,从而得到次小关键字。重复上述过程,将堆顶元素与堆中最后一个元素交换且调整,又得到了有n-2个元素的新堆。为节省存贮开销,可以把输出的最小(大)关键字放在Kn的位置上,把第二次输出的次小(大)关键字存放在Kn-1的位置上……,直至最大(小)关键字放在K1的位置上。如此,我们已经可以在初始情况为堆的情况下完成排序,那么,如何建立起初始堆呢?建初始小顶堆47369112533024851236854724305391元素序列为:47,36,53,91,12,30,24,85建立小顶堆建初始堆4736911253302485(a)4736851253302491(b)从最后一个具有叶子的结点(编号[n/2])开始建子堆,依次考查结点[n/2]-1,[n/2]-2,...,1等是否为堆,若否则调整为堆return建初始堆4736851224305391从最后一个具有叶子的结点(编号[n/2])开始建子堆,依次考查结点[n/2]-1,[n/2]-2,...,1等是否为堆,若否则调整为堆(C)4712853624305391(d)建初始堆1247853624305391从最后一个具有叶子的结点(编号[n/2])开始建子堆,依次考查结点[n/2]-1,[n/2]-2,...,1等是否为堆,若否则调整为堆(e)1236854724305391(f)当以下标1为根的完全二叉树为堆时,初始堆已建立也可以从空堆开始建初始堆10.4堆排序1964年弗洛伊德(Floyd)提出了筛选法建立初始堆,具体方法是:将待排序的关键字分放到一棵完全二叉树的各个结点中(此时完全二叉树并不一定具备堆的特性),显然,所有i≥[n/2]的结点Ki都没有子结点,以这样的Ki为根的子树已经是堆,因此初始建堆可从完全二叉树的第i个结点Ki开始(i=[n/2])。通过调整,逐步使以K[n/2],K[n/2]-1,K[n/2]-2,…为根的子树满足堆的定义,直至进行到以K1为根的树排成堆为止。在对Ki为根的子树建堆时,其子树已经为堆,要使得以Ki为根的完全二叉树为堆,则可能要调整父、子结点的位置,如此下一层已建成堆的子树可能不再满足堆的定义,则需继续进行调整,如此一次次递推下去,最多可能一直延伸到树叶。这种方法就像过筛子一样,把最小(大)的关键字一层一层地筛选出来,最后输出堆顶的最小(大)关键字。10.4堆排序算法(筛选算法)typedefSqListHeapType;//堆采用顺序存储结构voidHeapAdjust(HeapType&H,ints,intm){}//HeapAdjustrc=H.r[s];

for(j=2*s;j<=m;j*=2){//沿key较小的孩子结点向下筛选

if(j<m&&H.r[j].key>H.r[j+l].key)++j;

//j为key较小的记录的下标

if(rc.Key<H.r[j].key)break;

H.r[s]=H.r[j];//较小的孩子结点值换到父结点位置

s=j;}H.r[s]=rc;//rc应插入的位置在s处//H.r[s..m]中除H.r[s].key外均满足堆的定义

//调整H.r[s]的关键字,使H.r[s..m]成为一个小顶堆示例10.4堆排序算法(续)typedefSqListHeapType;//堆采用顺序存储结构voidHeapSort(HeapType&H){//对顺序表H进行堆排序

for(i=H.length/2;i>0;--i)//把H建成大顶堆

HeapAdjust(H,i,H.length);for(i=H.length;i>1;--i){H.r[1]←→H.r[i];//堆顶记录和当前未排子序列中最后一个记录相交换

HeapAdjust(H,1,i-1);//将H.r[l..i-1]重新调整为大顶堆

}}//HeapSort

for(i=H.length/2;i>0;--i)//把H建成大/小顶堆

HeapAdjust(H,i,H.length);for(i=H.length;i>1;--i){H.r[1]←→H.r[i];//堆顶记录和当前未排子序列中最后一个记录相交换

HeapAdjust(H,1,i-1);//将H.r[l..i-1]重新调整为大/小顶堆

}示例473691125330248510.4堆排序算法分析由于是完全二叉树,所以采用顺序存储结构时间复杂度:O(nlog2n)堆排序的整个算法时间是由建立初始堆和不断调整堆这两部分时间代价构成的,建立初始堆时关键字的比较次数不超过4n,不断调整堆的时间不超过O(nlog2n)。空间复杂度:O(1)只需要

1个辅助单元进行交换不稳定的排序方法对于记录数较少的序列来说,堆排序的优越性并不明显,但对数量大的序列来说堆排序是很有效的。归并排序筛选过程示例下面是一个大顶堆,输出堆顶元素后,将剩余元素重新调整为一个新堆的方法是:从树根开始,若以某结点为根的子树不是堆,则将其孩子中的较大者与之交换,即将非堆的子树推向叶子方向9153364785302412交换堆顶元素与序列末端的元素比较比较-交换return(a)大顶堆1253364785302491(b)8553364712302491(c)8553364730122491(d)大顶堆比较比较交换筛选过程示例下面是一个大顶堆,输出堆顶元素后,将剩余元素重新调整为一个新堆的方法是:从树根开始,若以某结点为根的子树不是堆,则将其孩子中的较大者与之交换,即将非堆的子树推向叶子方向9153364785302412交换堆顶元素与序列末端的元素比较比较-交换return(a)小顶堆1253364785302491(b)8553364712302412(c)小顶堆交换堆顶元素与序列末端的元素5336854791302412(d)10.5归并排序归并所谓“归并”,是将两个或两个以上的有序序列合并成为一个新的有序序列。从第二章线性表的讨论中得知,将两个有序表归并为一个有序表,无论是顺序存储结构还是链式存储结构,都是容易实现的。利用归并的思想可以进行排序。归并排序可将由n个记录的一个无序序列看成是由n个长度为1的有序子序列组成的序列,然后进行两两归并,得到[n/2]个长度为2或1的有序子序列,再两两归并,……,如此重复,直到最后形成包含n个记录的一个有序序列为止。这种总是反复将两个有序序列归并成一个有序序列的排序方法称为两路归并排序。有序序列R[l..n]有序子序列R[l..m]有序子序列R[m+1..n]2-路归并10.5两路归并过程示例设初始关键字序列为:4834608075122648*[48][34][6O][80][75][12][26][48*]第一趟归并:[34,48,60,80]第三趟归并:

[12,26,34,48,48*,60,75,80][60,80][12,75][26,48*][12,26,48*,75][34,48]第二趟归并:合并两个序列时,将合并得到的序列与原序列分开存放也可以用分治思路处理10.5先分解再归并设初始关键字序列为:[4834608075122648*][48346O8075122648*][75122648*][48346080][4834][6080][7512][2648*][48][34][60][80][75][12][26][48*][3448][6080][1275][2648*][122648*75][34486080][1226344848*607580]分解归并10.5两路归并排序算法递归算法voidMergeSort(待排序列){//归并排序

if(待排序列长度>1){MergeSort(待排序列的前半区间);

MergeSort(待排序列的后半区间);已排好序的前半区间、后半区间合并成一个有序序列;

}//if}//MergeSort采用顺序存储结构,对于由下标s~t界定的一个序列,前半区间为s~(s+t)/2,后半区间为(s+t)/2+1~tvoidMergeSort(SqList&L,ints,intt){//归并排序

if(s<t){m=(s+t)/2;MergeSort(L,s,m);

MergeSort(L,m+1,t);

Merge(L,s,m,t);//合并L.r[s]~L.r[m]与L.r[m+1]~L.r[t]}//if}//MergeSort10.5两路归并算法以顺序表作为存储结构voidMerge(SqList&L,inti,intm,intn){//两路归并

//引入辅助数组空间temp,有序序列为r[i..m]和r[m+1..n]}//Mergeb=i;for(j=m+1,k=1;i<=m&&j<=n;++k){

}//for//ifi]if(L.r[i].key<L.r[j].key)temp.r[k]=L.r[i++];elsetemp.r[k]=L.r[j++];for(;i<=m;)temp.r[k++]=L.r[i++];for(;j<=n;)temp.r[k++]=L.r[j++];

for(i=b,k=1;i<=n;)L.r[i++]=temp.r[k++];10.5归并排序算法分析以顺序表作为存储结构的归并排序算法时间复杂度:O(nlogn)对于具有n个元素的一个序列,元素的移动次数可以使用下面的递归公式计算:M(1)=0M(n)=2M(n/2)+2n空间复杂度:O(n)这是归并排序的严重缺点稳定的排序方法用迭代取代递归进行归并排序效率更高10.6基数排序基数排序(RadixSorting)基数排序是一种借助多关键字排序的思想对单逻辑关键字进行排序的方法,不需要进行记录关键字间的比较。例如:整理扑克牌、图书馆卡片的排序多关键字排序:n个元素的序列{R1,R2,...,Rn},每个元素Ri有d个关键字(K0i,K1i,...,Kd-1i),则序列对关键字(K0i,K1i,...,Kd-1i)有序是指:对于序列中任意两个记录Ri和Rj记都满足下列有序关系:

(K0i,K1i,...,Kd-1i)<(K0j,K1j,...,Kd-1j)

其中K0称为最主位关键字,Kd-1称为最次位关键字。10.6基数排序多关键字排序最高位优先(MSD):先对最主位关键字K0进行排序,将序列分成若干个子序列,每个子序列中的元素具有相同的K0值,然后分别就每个子序列对关键字K1进行排序,按K1值的不同再分成更小的子序列,依次重复,直至对Kd-2进行排序之后得到的每个子序列中的元素都具有相同的(K0,K1,...,Kd-2),而后分别对每个子序列对Kd-1

进行排序,最后将所有子序列依次联接在一起成为一个有序序列。最低位优先(LSD):先对最次位关键字Kd-1进行排序,然后对Kd-2进行排序,依次重复,直至对K0进行排序后便形成一个有序序列。10.6链式基数排序链式基数排序借助“分配”和“收集”两种操作对单逻辑关键字进行排序的方法。有的单逻辑关键字可以看成是由若干个关键字复合而成。例如,若关键字是数值,且其值都在0≤K≤999范围内,则可把每一个十进制数字看成一个关键字,即可认为K由3个关键字(K0,K1,K2)组成,其中,K0是百位数,K1是十位数,K2是个位数,且0≤Ki≤9(RADIX=10),由于每个关键字的范围相同,则按LSD进行排序,只要从最低数位关键字起,按关键字的不同值将序列中记录“分配”到RADIX个队列中后再“收集”之,如此重复d次,即可实现基数排序。具体来讲,第一次对最低位关键字(个位数)进行分配,将初始序列中的元素分配到RADIX个队列中去,每个队列中的元素的关键字的个位数相同。用f[i]和e[i]分别表示第i个队列的头指针和尾指针。10.6链式基数排序278用链表表示元素序列109063930589184505269008083e[0]e[1]e[2]e[3]e[4]e[5]e[6]e[7]e[8]e[9]f[0]f[1]f[2]f[3]f[4]f[5]f[6]f[7]f[8]f[9]278109063930589184505269008083分配1收集193006308318450527800810958926910.6链式基数排序(续)用链表表示元素序列e[0]e[1]e[2]e[3]e[4]e[5]e[6]e[7]e[8]e[9]f[0]f[1]f[2]f[3]f[4]f[5]f[6]f[7]f[8]f[9]083930505184269分配2收集250500810993006326927808318458993006308318450527800810958926906327800810958910.6链式基数排序(续)用链表表示元素序列e[0]e[1]e[2]e[3]e[4]e[5]e[6]e[7]e[8]e[9]f[0]f[1]f[2]f[3]f[4]f[5]f[6]f[7]f[8]f[9]083930505184278分配3收集300806308310918426927850558993006326900810958950500810993006326927808318458910.6链式基数排序方法分析采用链式存储,避免元素移动时间复杂度:O(d(n+rd)),rd为每个关键字的基数每一趟分配:O(n)每一趟收集:O(rd)共d趟:d(n+rd)

空间复杂度:O(rd)2rd个队列指针稳定的排序方法适用于元素数目n很大且关键字很小的情况10.7内部排序方法分析与比较时间复杂度空间复杂度稳定性适用情况链式基数排序2-路归并排序堆排序简单选择排序快速排序冒泡排序希尔排序直接插入排序O(n2)O(1)稳定n较小,初始序列基本有序O(n1.3)O(1)不稳定O(n2)O(1)稳定n较小,初始序列基本有序O(nlog2n)O(log2n)不稳定初始序列无序O(n2)O(1)不稳定n较小O(nlog2n)O(1)不稳定n较大,或只排前几位O(nlog2n)O(n)稳定n很大O(d(n+rd))O(rd)稳定n大,关键字值小10.7内部排序方法分析与比较内部排序可能达到的最快速度是多少?在已经讨论过的方法中,最坏情况下的时间复杂度为O(n2)或O(nlog2n),因此排序速度的上界是O(n2),那么O(nlog2n)是否为其下界呢?下面先看一个例子。K3<k2<k1K1<k2K1<k3K2<k3K2<k3<k1否否否是K2<k1<k3K2<k3K1<k3否K1<k2<k3是是K3<k1<k2K1<k3<k2否是10.7内部排序方法分析与比较若二叉树的高度为h,则其叶子结点数目不超过2h-1;反之,若二叉树中有u个叶子结点,则二叉树的高度至少为log2u+1(向上取整),因此任何一个借

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论