一、概述
1、定义
- 将文件中的数据记录按关键字值的递增或递减的顺序排列起来。
- {R1, R2,..., Rn}→ {Ri1, Ri2,..., Rin}
- 其中关键字{k1, k2,..., kn}→有序序列{ki1, ki2,..., kin}
2、排序方法的稳定性:
- 对于ki=kj的记录Ri=Rj( Ri在Rj之前),
- 排序后: Ri仍在Rj之前,则排序方法是稳定的;
- Ri在Rj之后, 则排序方法是不稳定的;
3、方法分类
-
内部排序:在内存中进行,适于小文件
文件可有三种存储结构
(1)一维数组:对记录的物理位置重排
(2)链表:修改指针
(3)索引表:对索引进行物理重排,记录位置不动(由于不方便移动等原因)
-
外部排序:使用内存和外存,适于大文件,内存不够用
本讲仅考虑:记录数组R[n],关键字key为整数
-
标准
执行时间(最重要的标志),所需空间,算法复杂度
-
排序的时间代价
算法中关键字的比较次数和记录的移动次数
- 以记录数组作为文件的存储结构,关键字为整数,文件类型说明如下
二、插入排序(直接插入排序、希尔排序)
1、直接插入排序(最简单)——稳定
(1)具体做法:
把第i个记录Ri插入到有序区{R1, R2, …, Ri-1};将关键字ki依次与关键字ki-1, ki-2, …, k1进行比较,从而找到应该插入的位置,然后将ki插入。
(2)算法采用的是查找比较操作和记录移动操作交替进行的方法 ,具体做法是:
将待插入记录R[i]的关键字依次与有序区中记录R[j](j=i-1, i-2, …, 1)的关键字进行比较,若R[j]的关键字大于R[i]的关键字,则将R[j]后移一个位置;若R[j]的关键字小于或等于R[i]的关键字,则查找过程结束,j+1即为R[i]的插入位置。因为关键字比R[i]大的记录均已后移,故只要将R[i]插入该位置即可。
(3)附加记录R[0]/哨兵,其作用有两个
①进入查找循环之前,它保存了R[i]的副本,使得不致于因记录的后移而丢失R[i]中的内容; ②在while循环中“监视”下标变量j是否越界,以避免循环内每次都要检测j是否越界。因此,我们将R[0]称为“监视哨”,这使得测试循环条件的时间大约减少一半。
(4)直接插入排序的算法分析
- 整个排序过程只有两种运算,即比较关键字和移动记录。
- 外循环:n-1趟插入排序;
- 内循环:每一趟排序所需进行的关键字的比较和记录的后移。
- 文件正序时:每趟排序的关键字比较次数为1,总的比较次数Cmin=n-1;
- 每趟记录移动次数是2次,总的移动次数Mmin=2(n-1);
- 文件逆序时:关键字的比较次数和记录移动次数均取最大值。
- 每趟进行i次比较:与前i-1个记录及“监视哨” 进行比较
- 每趟排序中除了上面提到的两次移动外,还需将关键字大于R[i]的记录后移一个 位置。
- 因此,总的比较次数和记录的移动次数为:
- 在随机情况下:
关键字各种排列出现的概率相同,则可取上述两种情况的平均值作为比较和记录移动的平均次数,约为(n^2)/4。由此,直接插入排序的时间复杂度为
O(n2),空间复杂度为O(1)。
(5)算法参考
2、希尔排序——缩小增量排序
(1)基本思想:
先取一个小于n的整数d1并作为第一个增量,将文件的全部记录分成d1个组,所有距离为d1倍数的记录放在同一个组中,在各组内进行直接插入排序;然后取第二个增量d2<d1,重复上述的分组和排序,直至所取的增量dt=1(dt<dt-1<…<d2<d1)为止,此时,所有的记录放在同一组中进行直接插入排序。
三、选择排序 (直接选择排序)
1、基本思想概述
每一趟在待排序的记录中选出关键字最小的记录,依次放在已排序的记录序列的最后,直至全部记录排完为止。
直接选择排序和堆排序都归属于此类排序。 本节主要介绍直接选择排序。
2、直接选择排序——不稳定
(1)基本思想
- 第一趟排序是在无序区R[0]~R[n-1]中选出最小的记录,将它与R[0]交换;
- 第二趟排序是在无序区R[1]~R[n-1]中选关键字最小的记录,将它与R[1]交换;
- 第i趟排序时R[0]~R[i-2]已是有序区,在当前的无序区R[i-1]~R[n-1]中选出关键字最小的记录R[k],将它与无序区中第1个记录R[i-1]交换,使R[1]~R[i-1]变为新的有序区。
- 因为每趟排序都使有序区中增加了一个记录,且有序区中的记录关键字均不大于无序区中记录的关键字,所以,进行n-1趟排序后,整个文件就是递增有序的。
(2)算法参考
(3)结论
- 直接选择排序的比较次数与关键字的初始排列状态无关,第一趟找出最小关键字需要进行n-1次比较,第二趟找出次小关键字需要进行n-2次比较……。因此,总的比较次数为
- 由于每趟选择后要进行两个记录的交换,而每次交换要进行三次记录的移动,因此,对n个记录进行直接选择排序时,记录移动次数的最大值为3(n1),最小值为0。
- 直接选择排序的时间复杂度为O(n^2)。
四、交换排序(起泡排序、快速排序)
1、基本思想:
- 两两比较待排序记录的关键字,发现两个记录逆序时即进行交换,直至没有逆序的记录为止。
- 两种方法:起泡排序和快速排序
2、起泡排序
(1)基本思想
通过对相邻关键字的比较和交换,使全部记录排列有序。
(2)过程
- 将关键字按纵向排列,然后自下而上地对每两个相邻的关键字进行比较,若为逆序( 即 k j-1>kj),则将两个记录交换位置。这样的操作反复进行,直至全部记录都比较、交换完为止。如此一趟排序后,就将关键字最小的记录安排在第一个记录的位置上。
- 对后n-1个记录重复同样操作,再将次小关键字记录安排在第二个记录的位置上。
- 重复上述过程,直至没有记录需要交换为止。
- 引入一个布尔量noswap,在每趟排序之前,先将它置为TRUE。在一趟排序结束时,我们再检查noswap,若未曾交换过记录便终止算法。
(3)算法参考
(4)结论
- 若文件按关键字递增有序,则只需进行一趟排序,比较次数为n-1,记录移动次数为0,即比较次数和记录移动次数均为最小值;
- 若文件按关键字递减有序,则需进行n-1趟排序,比较次数和记录移动次数均为最大值,分别为:
- 起泡排序的时间复杂度为O(n^2)。
(5)改进
- ①在每趟扫描中,记住最后一次交换发生的位置k,因为该位置之前的记录都已排序,所以下一趟排序可终止于位置k,而不必进行到预定的下界i。例如:1,2,3,9,8,7。
- ②上面提到若干文件初态为正序时只需进行一趟扫描,初态为逆序时则需进行n-1趟扫描。 实际上,如果只有最轻的气泡位于文件最末的位置,其余的记录均已排好序,那么也只需一趟扫描就可以完成排序。例如对于初始关键字序列6, 9, 12, 14, 19, 22, 35, 2就仅需一趟扫描。 而当只有最重的气泡位于文件首记录的位置,其余的记录均已排好序时,则仍需n-1趟扫描才能完成排序。例如对于初始关键字35, 2, 6, 9, 12, 14, 19, 22就需七趟扫描。
3、快速排序——目前基于比较的内部排序方法中速度最快的
任取待排序记录序列中的某个记录 (例如取第一个记录) 作为枢轴(pivot),以该记录的关键字作为基准,将整个记录序列划分为左右两个子序列:
- 左侧子序列中所有记录的关键字都小于或等于枢轴记录的关键字
- 右侧子序列中所有记录的关键字都大于枢轴记录的关键字
- 枢轴记录则排在这两个子序列中间(这也是该记录最终应安放的位置)。
- 然后分别对这两个子序列重复施行上述方法,直到所有的记录都按关键字有序排在相应位置上为止。
(2)算法参考
①递归
②划分(对无序区按基准进行划分)
五、归并排序
1、算法思想
假设初始表含有n个记录,则可看成是n个有序的子表,每个子表的长度为1,然后两两归并,得到-【n/2】个长度为2或1的有序子表,再两两归并,……,如此重复,直至得到一个长度为n的有序子表为止。
2、归并过程——稳定
- 将两个有序子表合并为一个有序子表的过程类似于玩扑克牌:假设桌上有两堆面朝上的牌,最小的排在上面,现要将这两堆牌(看作输入)合并成一堆有序的牌(看作输出)。基本步骤是比较两输入堆顶上的两张牌,取出较小的那张牌将它面朝下放到输出堆中。重复这一步骤,直至某一输入堆为空,这是将另一输入堆中剩余的牌面朝下全部放入到输出堆中即可。如图所示:
- 设两个有序子表(相当于输入堆)放在同一向量中相邻的位置上:R[low..mid],R[mid+1..high],先将它们合并到一个局部的暂存变量R1(相当于输出堆)中,待合并完成后将R1复制回R[low..high]中。
(3)二路归并算法参考
一趟归并算法
- 第1趟归并排序时,将R[1..n]看成是 n 个长度为 1 的有序子序列 (归并项),先做两两归并,得到 [[n / 2]个长度为 2 的归并项 (如果 n 为奇数,则最后一个有序子序列的长度为1);
- 再做两两归并,…,如此重复,最后得到一个长度为 n 的有序序列。
——代码可直接调用
六、内部排序方法的比较
1、各排序方法的综合比较
表中的“简单排序”包括除希尔排序之外的所有插入排序、起泡排序和直接选择排序,其中直接插入排序最为简单。
2、选取排序方法时考虑的因素有:
① 待排序的记录个数n |
② 记录本身的大小 |
③ 关键字的分布情况 |
④ 对排序稳定性的要求 |
⑤ 语言工具的条件,辅助空间的大小 |
3、对应选择
(1) 若待排序的一组记录数目n较小(如n≤50)时,可采用插入排序和选择排序。
- 由于直接插入排序所需的记录移动操作较直接选择排序多,因而当记录本身信息量较大时,用直接选择排序较好。
(2) 若n较大,则应采用时间复杂度为O(nlog2n)的排序方法:快速排序、堆排序或归并排序。
- 快速排序被认为是目前内部排序中是最好的方法,当待排序的关键字随机分布时,快速排序的平均运行时间最短;
- 堆排序只需一个记录的辅助空间,且不会出现快速排序可能出现的最坏情况。然而这两种方法都是不稳定的排序方法。
- 若要求排序稳定,可选用归并排序,但本章介绍的从单个记录起进行两两归并的排序算法并不值得提倡。归并排序通常可以和直接插入排序结合起来使用。先利用直接插入排序求得较长的有序子文件,然后再两两归并之。因为直接插入排序是稳定的,所以改进后的归并排序仍是稳定的。