数据结构(严蔚敏) 第10章排序.ppt
《数据结构(严蔚敏) 第10章排序.ppt》由会员分享,可在线阅读,更多相关《数据结构(严蔚敏) 第10章排序.ppt(83页珍藏版)》请在淘文阁 - 分享文档赚钱的网站上搜索。
1、第10章 排序 10.1 概 述 排序(Sorting)是计算机程序设计中的一种重要操作,它的功能是将一个数据元素(或记录)的任意序列,重新排列成一个按关键字有序的序列。从第9章的讨论中容易看出,为了查找方便,通常希望计算机中的表是按关键字有序的。又如建造树表(无论是二叉排序树或B-树)的过程本身就是一个排序的过程。因此,学习和研究各种排序方法是计算机工作者的重要课题之一。为了便于讨论,在此首先要对排序下一个确切的定义:假设含n个记录的序列为 R1,R2,Rn (10-1)其相应的关键字序列为 K1,K2,Kn (10-2)需确定1,2,n的一种排列p1,p2,pn,使其 相应的关键字满足如下
2、的非递减(或非递增)关系 Kp1Kp2Kpn 即使式(10-1)的序列成为一个按关键字有序的序列 Rp1,Rp2,Rpn 这样一种操作称为排序。上述排序定义中的关键字Ki可以是记录Ri(i=1,2,n)的主关键字,也可以是记录Ri的次关键字,甚至是若干数据项的组合。若Ki是主关键字,则任何一个记录的无序序列经排序后得到的结果是唯一的;若Ki是次关键字,则排序的结果不唯一,因为待排序的记录序列中可能存在两个或两个以上关键字相等的记录。假设Ki=Kj(1i n,1jn,i!=j),且在排序前的序列中Ri领先于Rj(即ij)。若在排序后的序列中 Ri仍领先于Rj,则称所用的排序方法是稳定的;反之,若
3、可能使排序后的序列中Rj领先于Ri,则称所用的排序方法是不稳定的。由于待排序的记录数量不同,使得排序过程中涉及的存储器不同,可将排序方法分为两大类:一类是内部排序,指的是待排序记录存放在计算机随机存储器中进行的排序过程;另一类是外部排序,指的是待排序记录的数量很大,以致内存一次不能容纳全部记录,在排序过程中尚需对外存进行访问的排序过程。本章集中讨论内部排序。内部排序的方法很多,每一种方法都有各自的优缺点,适合在不同的环境(如记录的初始排列状态等)下使用。如果按排序过程中依据的不同原则对内部排序方法进行分类,则大致可分为插入排序、交换排序、选择排序、归并排序和计数排序等五类;如果按内部排序过程中
4、所需的工作量来区分,则可分为三类:(1)简单的排序方法,其时间复杂度为O(n2);(2)先进的排序方法,其时间复杂度为O(nlogn);(3)基数排序,其时间复杂度为O(dn)。10.2 插入排序 一、直接插入排序 直接插人排序(Straight Insertion Sort)是一种最简单的排序方法,它的基本操作是将一个记录插入到已排好序的有序表中,从而得到一个新的、记录数增1的有序表。例如,已知待排序的一组记录的初始排列如下所示:R(49),R(38),R(65),R(97),R(76),R(13),R(27),R(49),(10-4)假设在排序过程中,前4个记录已按关键字递增的次序重新排列
5、,构成一个含4个记录的有序序列 R(38),R(49),R(65),R(97)(10-5)现要将式(10-4)中第5个(即关键字为76的)记录插入上述序列,以得到一个新的含5个记录的有序序列,则首先要在式(10-5)的序列中进行查找以确定R(76)所应插入的位置,然后进行插入。假设从R(97)起向左进行顺序查找,得到下列新的有序序列 R(38),R(49),R(65),R(76),R(97)(10-6)称从式(10-5)到式(10-6)的过程为一趟直接插入排序。一般情况下,第i趟直接插入排序的操作为:在含有i-1个记录的有序子序列r1.i-1中插入一个记录ri后,变成含有i个记录的有序子序列r
6、1.i;并且,和顺序查找类似,为了在查找插入位置的过程中避免数组下标出界,在r0处设置监视哨。在自i-1起往前搜索的过程中,可以同时后移记录。整个排序过程为进行n-1趟插入,即:先将序列中的第1个记录看成是一个有序的子序列,然后从第2个记录起逐个进行插入,直至整个序列变成按关键字非递减有序序列为止。直接插人排序算法描述:void InsertSort(SqList&L)/对顺序表L作直接插入排序。for(i=2;i=L.length;+i)if LT(L.ri.key,L.ri-1.key)/“”,需将L.ri插入有序子表 L.r0=L.ri /复制为哨兵 for(j=i-1;LT(L.r0.
7、key,L.rj.key);-j)L.rj+1=L.rj;/记录后移 L.rj+1=L.r0 /插入到正确位置 /Insertsort 算法 10.1 初始关键字:(49)38 65 97 76 13 27 49 i=2 (38)(38 49)65 97 76 13 27 49 i=3 (65)(38 49 65)97 76 13 27 49 i=4 (97)(38 49 65 97)76 13 27 49 i=5 (76)(38 49 65 76 97)13 27 49 i=6 (13)(13 38 49 65 76 97)27 49 i=7 (27)(13 27 38 49 65 76 9
8、7)49 i=8 (49)(13 27 38 49 49 65 76 97)监视哨L.r0 图10.1 直接插入排序示例 直接插入排序的算法简洁,容易实现,那么它的效率如何呢?从空间来看,它只需要一个记录的辅助空间,从时间来看,排序的基本操作为比较两个关键字的大小和移动记录。当待排序列中记录按关键字非递减有序排列(以下称之为“正序”)时,所需进行关键字间比较的次数达最小值n-1,记录不需移动;反之,当待排序列中记录按关键字非递增有序排列(以下称之为“逆序”)时,总的比较次数达最大值(n+2)(n-1)/2。记录移动的次数也达最大值(n+4)(n-1)/2。若待排序记录是随机的,即待排序列中的记
9、录可能出现的各种排列的概率相同,则我们可取上述最小值和最大值的平均值,作为直接插入排序时所需进行关键字间的比较次数和移动记录的次数,约为n2/4。由此,直接插入排序的时间复杂度为O(n2)。二、其它插人排序 从上一节的讨论中可见,直接插入排序算法简便,且容易实现。当待排序记录的数量 n很小时,这是一种很好的排序方法。但是,通常待排序序列中的记录数量n很大,则不宜采用直接插入排序。由此需要讨论改进的办法。在直接插入排序的基础上,从减少“比较”和“移动”这两种操作的次数着眼,可实现其他的插入排序的方法。1、折半插入排序 由于插入排序的基本操作是在一个有序表中进行查找和插入,则从9.1节的讨论中可知
10、,这个“查找”操作可利用“折半查找”来实现,由此进行的插入排序称之为折半插入排序(BinarylnsertionSort),其算法如算法10.2所示。void BInsertSort(SqList&L)/对顺序表L作折半插入排序。for(i=2;iL.length;+i)L.r0=L.ri /L.ri暂存到L.r0 low=1;high=i-1;/在rlow.high中折半查找有序插入的位置 while(low=high+1;-j)L.rj+1=L.rj;/记录后移 L.rhigh+1=L.r0;/插入 /for/BInsertSort 算法10.2 折半插入排序所需附加存储空间和直接插入排序
11、相同,从时间上比较,折半插入排序仅减少了关键字间的比较次数,而记录的移动次数不变。因此,折半插入排序的时间复杂度仍为O(n2)。2、2-路插入排序 2-路插入排序是在折半插入排序的基础上再改进之,其目的是减少排序过程中移动记录的次数,但为此需要n个记录的辅助空间。具体做法是:另设一个和L.r同类型的数组d,首先将L.r1赋值给d1,并将d1看成是在排好序的序列中处于中间位置的记录,然后从L.r中第2个记录起依次插入到d1之前或之后的有序序列中。先将待插记录的关键字和d1的关键字进行比较,若Lri.keyd1.key,则将L.ri插入到d1之前的有序表中。反之,则将L.ri插入到d1之后的有序表
12、中。在实现算法时,可将d看成是一个循环向量,并设两个指针first和final分别指示排序过程中得到的有序序列中的第一个记录和最后一个记录在d中的位置。初始关键字:49 38 65 97 76 13 27 49*排序过程中d的状态如下:i=1:(49)first finali=2:(49)(38)final firsti=3:(49 65)(38)final firsti=4:(49 65 97)(38)final firsti=5:(49 65 76 97)(38)final firsti=6:(49 65 76 97)(13 38)final first i=7:(49 65 76 97)
13、(13 27 38)final first i=8:(49 49*65 76 97 13 27 38)final first 图10.2 2-路插入排序示例 在2-路插入排序中,移动记录的次数约为n2/8。因此,2-路插入排序只能减少移动记录的次数,而不能绝对避免移动记录。并且,当L.r1是待排序记录中关键字最小或最大的记录时,2-路插入排序就完全失去它的优越性。因此,若希望在排序过程中不移动记录,只有改变存储结构,进行表插入排序。3、表插入排序 用静态链表做为待排序记录序列的存储结构。设数组中下标为“0”的分量为表头结点,并令表头结点记录的关键字取最大整数MAXINT。则表插入排序的过程描述
14、如下:首先将静态链表中数组下标为“1”的分量(结点)和表头结点构成一个循环链表,然后依次将下标为“2”至“n”的分量(结点)按记录关键字非递减有序插入到循环链表中。0 1 2 3 4 5 6 7 8 MAXINT4938659776132749*10-MAXINT4938659776132749*681504723 从表插入排序的过程可见,表插入排序的基本操作仍是将一个记录插入到已排好序的有序表中。和直接插入排序相比,不同之处仅是以修改2n次指针值代替移动记录,排序过程中所需进行的关键字间的比较次数相同。因此,表插入排序的时间复杂度仍是 O(n2)。另一方面,表插入排序的结果只是求得一个有序链
15、表,则只能对它进行顺序查找,不能进行随机查找,为了能实现有序表的折半查找,尚需对记录进行重新排列。三、希尔排序 希尔排序(Shells Sort)又称“缩小增量排序”(Diminishing Increment Sort),它也是一种属插入排序类的方法,但在时间效率上较前述几种排序方法有较大的改进。从对直接插入排序的分析得知,其算法时间复杂度为O(n2),但是,若待排记录序列为“正序”时,其时间复杂度可提高至O(n)。由此可设想,若待排记录序列按关键字“基本有序”,即序列中具有下列特性 L.ri.key MaxL.rj.key (10-7)1ji 的记录较少时,直接插入排序的效率就可大大提高,
16、从另一方面来看,由于直接插入排序算法简单,则在n值很小时效率也比较高。希尔排序正是从这两点分析出发对直接插入排序进行改进得到的一种插入排序方法。它的基本思想是:先将整个待排记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行一次直接插入排序。初始关键字:49 38 65 97 76 13 27 49*55 04一趟排序结果:13 27 49*55 04 49 38 65 97 76 二趟排序结果:13 04 49*38 27 49 55 65 97 76三趟排序结果:04 13 27 38 49*49 55 65 76 97 从上述排序过程可见,希
17、尔排序的一个特点是:子序列的构成不是简单地“逐段分割”,而是将相隔某个“增量”的记录组成一个子序列。如上例中,第一趟排序时的增量为5,第二趟排序时的增量为3,由于在前两趟的插入排序中记录的关键字是和同一子序列中的前一个记录的关键字进行比较,因此关键字较小的记录就不是一步一步地往前挪动,而是跳跃式地往前移,从而使得在进行最后一趟增量为1的插入排序时,序列已基本有序,只要作记录的少量比较和移动即可完成排序,因此希尔排序的时间复杂度较直接插入排序低。下面用算法语言描述上述希尔排序的过程,为此先将算法10.1改写成如算法 10.4所示的一般形式。希尔排序算法如算法10.5所示。void ShellIn
18、sert(SqList&L,int dk)/对顺序表L作一趟希尔插入排序。/本算法是和一趟直接插入排序相比,作了以下修改:/1、前后记录位置的增量是dk,而不是1;/2、r0只是暂存单元,不是哨兵。当j=0时,插入位置已找到。for(i=dk+1;i0<(L.r0.key,L.rj.key);j-=dk)L.rj+dk=L.rj;/记录后移,查找插入位置 L.rj+dk=L.r0;/插入 /if/ShellInsert 算法10.4 void ShellSort(Sqlist&L,int dlta,int t)/按增量序列dlta0.t-1对顺序表L作希尔排序。for(k=0;kL.r2.
19、key),则将两个记录交换之,然后比较第二个记录和第三个记录的关键字。依次类推,直至第n-1个记录和第n个记录的关键字进行过比较为止。上述过程称作第一趟起泡排序,其结果使得关键字最大的记录被安置到最后一个记录的位置上。然后进行第二趟起泡排序,对前n-1个记录进行同样操作,其结果是使关键字次大的记录被安置到第n-1个记录的位置上。.一般地,第i趟起泡排序是从L.r1到L.rn-i+1依次比较相邻两个记录的关键字,并在“逆序”时交换相邻记录,其结果是这n-i+1个记录中关键字最大的记录被交换到第n-i+1的位置上。整个排序过程需进行k(1=kn)趟起泡排序,显然,判别起泡排序结束的条件应该是“在一
20、趟排序过程中没有进行过交换记录的操作”。49 38 38 38 38 13 13 38 49 49 49 13 27 27 65 65 65 13 27 38 38 97 76 13 27 49 49 76 13 27 49*49*13 27 49*65 27 49*76 49*97 图10.6展示了起泡排序的一个实例 分析起泡排序的效率,容易看出,若初始序列为“正序”序列,则只需进行一趟排序,在排序过程中进行n-1次关键字间的比较,且不移动记录;反之,若初始序列为“逆序”序列,则需进行n-1趟排序,需进行n(n-1)/2次比较,并作等数量级的记录移动。因此,总的时间复杂度为O(n2)。快速排
21、序(Quick Sort)是对起泡排序的一种改进。它的基本思想是,通过一趟排序将待排记录分割成独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。假设待排序的序列为L.rs,L.rs+1,L.rt,首先任意选取一个记录(通常可选第一个记录L.rs作为枢轴(或支点)(pivot),然后按下述原则重新排列其余记录:将所有关键字较它小的记录都安置在它的位置之前,将所有关键字较它大的记录都安置在它的位置之后。这个过程称作一趟快速排序(或一次划分)。一趟快速排序的具体做法是:附设两个指针low和high,它们的初值分别为low和high
22、,设枢轴记录的关键字为pivotkey,则首先从high所指位置起向前搜索找到第一个关键字小于pivotkey的记录和枢轴记录互相交换,然后从low所指位置起向后搜索,找到第一个关键字大于pivotkey的记录和枢轴记录互相交换,重复这两步直至low=high为止。其算法如算法10.6(a)所示。int Partition(SqList&L,int low,int high)/交换顺序表L中子表L.rlow.high的记录,使枢轴记录到位,/并返回其所在位置,此时在它之前(后)的记录均不大(小)于它。pivotkey =L.rlow.key;/用子表的第一个记录作枢轴记录 while(lowh
23、igh)/从表的两端交替地向中间扫描 while(lowpivotkey)-high;L.rlowL.rhigh;/将比枢轴记录小的记录交换到低端 while(lowhigh&L.rlow.keypivotkey)+low;L.rlowL.rhigh;/将比枢轴记录大的记录交换到高端 return low;/返回枢轴所在位置/partition 算法10.6(a)改写上述算法,先将枢轴记录暂存在r0的位置上,排序过程中只作rlow或rhigh的单向移动,直至一趟排序结束后再将枢轴记录移至正确位置上。int Partition(SqList&L,int low,int high)/交换顺序表L中
24、子表rlowhigh的记录,/枢轴记录到位,并返回其所在位置,/此时在它之前(后)的记录均不大(小)于它。L.r0=L.rlow;pivotkey=L.rlow.key;/枢轴记录关键字 while(lowhigh)/从表的两端交替地向中间扫描 while(lowpivotkey)-high;L.rlow=L.rhigh;/将比枢轴记录小的记录移到低端 low while(lowhigh&L.rlow.keypivotkey)+low;L.rhigh=L.rlow;/将比枢轴记录大的记录移到高端 L.rlow=L.r0;/枢轴记录到位 return low;/返回枢轴位置/Partition快
25、速排序示例:初始关键字:49 38 65 97 76 13 27 49*low high进行1次交换之后:27 38 65 97 76 13 49*low high进行2次交换之后:27 38 97 76 13 65 49*low high进行3次交换之后:27 38 13 97 76 65 49*low high进行4次交换之后:27 38 13 76 97 65 49*low high high 完成一趟排序 27 38 13 49 76 97 65 49*初始状态:49 38 65 97 76 13 27 49*一次划分之后:27 38 13 49 76 97 65 49*分别进行快速排
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 数据结构严蔚敏 第10章 排序 数据结构 严蔚敏 10
限制150内