目录
外部排序的操作方法
对于记录很多的文件,无法将整个文件全部复制进内存中再进行排序。因此,只需待排序列的记录储存在外存中,排序时再把数据一部分一部分地调入内存进行排序,在排序过程中需要多次进行内存和外存之间的交换。这种排序方法就称为外部排序。
外存信息的存取:文件通常是,按块存储在磁盘上,操作系统也是按块对磁盘上的信息进行读写的。因为磁盘读/写的机械动作所需的时间远远超过内存运算的时间,因此在外部排序过程中的时间代价注意考虑访问磁盘的次数,即I/O次数。
外部排序通常采用归并排序法。它包括两个独立的阶段:
- 根据内存缓冲区的大小,将外存上的文件分成若干长度为 L 的子文件或段,依次读入内存并利用内部排序方法对它们进行排序,并将排序后的有序子文件重新写回外存,称这些有序子文件为归并段或顺串。
- 对这些归并段进行逐趟归并,并使归并段逐渐由小至大,直至得到整个有序文件为止。
对归并段进行逐趟归并的具体方法:我们在内存中开辟两个输入缓存区和一个输出缓存区
- 输入缓冲区:分别读入外存的两个归并段,然后归并它们,将结果放在输出缓存区。
- 输出缓冲区:存放归并的结果,但显然没法完全存放归并段的归并结果,因此需要在存满时再输出回磁盘。
由于我们将两个为输入缓冲区大小的归并段归并成一个长度更长的归并段放在外存,之后输入缓冲区读这个更长的归并段时不能一次读完,只需先读入缓冲区大小(空了立马继续读入)即可,不影响内存中的归并操作。
外部排序的时间优化
外部排序时间开销=读写外存的时间+内部排序(初始生成归并段)所需时间+内部归并所需时间。其中读写外存的时间占比大的多,所以我们优化外部排序主要考虑减少读写外存时间。
而进行一次外存读写的时间均值是由外存设备决定的,所以我们优化外部排序需要减少外存信息的读写次数d。
①多路平衡归并与败者树
作用:减少归并趟数与优化多路归并的选择问题
上图这是一个2路平衡归并,我们可以看出一共有四趟排好了整个大文件,而每一趟基本都要把所有记录都读/写一遍。对外存上信息的读/写是以“物理块”为单位的。假设每个物理块可以容纳200个记录,而待排文件有10000个记录,那么我们一趟归并就要“读”50次和“写”50次。四趟共400次加上初始生成归并段时进行的一趟读/写100次共计500次。
K路平衡归并:①最多只能有k个归并段归并成一个。
②每一趟归并中,若共有m个归并段参与归并,则经过这一趟处理得到 ⌈ m/k ⌉个新的归并段。
上图是一个5路平衡归并,我们可以看出进行了二趟归并就排好了整个大文件,则共计读写次数为2*100+100=300次。一般情况下, 对r个初始归并段进行k路平衡归并时,归并的趟数:
S=⌈ ⌉
可见,外存信息读次数d与归并趟数s有关,若增加k(路数)或减少r(归并段个数)便能减少趟数s。
多路归并带来的负面影响:①K路归并时,需要开辟k个输入缓存区,内存开销增加。
②每挑选一个关键字需要对比关键字k-1次,内部归并所需时间增加。(可用败者树优化)
做内部归并时,每趟归并n个元素需要做(n-1)(k-1)次比较,S趟归并总共需要的比较此数为
S(n-1)(k-1)=⌈ ⌉(n-1)(k-1)=⌈ ⌉(n-1)(k-1)/⌈ ⌉
其中,(k-1)/⌈ ⌉随k的增长而增长,败者树使内部排序不受k增长的影响。
败者树可视为一棵完全二叉树。完全二叉树的根结点的上方是“冠军”结点。
k个叶结点分别存放k个归并段在归并过程中当前参加比较的记录,
k-1个非终端结点用来记忆左右孩子结点中的“失败者”所在的归并段,而让胜者继续往上比较,一直到冠军结点。
(我们知道对于任意一棵二叉树,叶子结点数为,度为2的结点数为,则=+1。)
这样二叉树的所有非终端结点都记录“失败者”所在的归并段,“冠军”结点记录选择过的结点所在的归并段。
可见,使用败者树后,在k个记录中选择最小的关键字,最多需要 ⌈ ⌉次比较。总的比较次数可写为 S(n-1)(k-1)= ⌈ ⌉ × (n-1) × ⌈ ⌉ = ⌈ ⌉ (n-1)
使用败者树后,内部归并的比较次数与K无关了,只要内存空间允许,增大归并路数将有效减少归并树的高度,从而减少I/O次数。(k不能过大到减少输入缓冲区的容量,否则会增大r)。
②置换选择排序与最佳归并树
作用:减少初始归并段个数与组织各归并段的归并顺序
由S=⌈ ⌉ 可见,减少归并段的个数r是我们减少s的另一个途径。r= ⌈n/l ⌉。其中n是整个文件的记录数,l是初始归并段中的记录数。我们之前得到的初始归并段是在内存工作区中进行内部排序得到的,l取决于内存工作站的大小。若要减少r,则要增加l,则要探索新的排序方法。
置换-选择排序的特点是:在整个排序(得到初始归并段)过程中,选择最小(或最大)关键字和输入、输出交叉或平行进行。
置换-选择排序的操作过程为:
设内存工作区WA可容纳w个记录。外存中初始归并段输出文件为FO,外存中的初始待排文件为FI
- 从待排文件FI中输入w个记录到工作区WA。
- 从工作区WA中选出其中关键字取最小值的记录,记为MINMAX记录。
- 将MINMAX记录输出到外存的FO中。
- 若FI不空,则从FI输入下一个记录到工作区WA中。
- 从WA中所有关键字不比MINMAX记录小的关键字中选出最小的关键字,并作为新的MINMAX记录。
- 重复3~5,直到在WA中选不出新的MINMAX记录为止,由此得到一个初始归并段,输出一个归并段的结束标志到FO中去。
- 重复2~6,直至WA为空。由此得到全部的初始归并段。
下表用记录数为9的输入文件FI和大小为3个记录的工作区WA描述了这个过程。注意红色的是不满足步骤5的数,绿色的是满足步骤5将要输出到FO的数。
输入文件FI | 工作区WA | 输出文件FO |
17,21,05,44,10,12,56,32,29 | ||
44,10,12,56,32,29 | 17,21,05 | |
10,12,56,32,29 | 17,21,44 | 05 |
12,56,32,29 | 10,21,44 | 05,17 |
56,32,29 | 10,12,44 | 05,17,21 |
32,29 | 10,12,56 | 05,17,21,44 |
29 | 10,12,32 | 05,17,21,44,56 |
29 | 10,12,32 | 05,17,21,44,56,# |
29,12,32 | 10 | |
29,32 | 10,12 | |
32 | 10,12,29 | |
10,12,29,32 | ||
10,12,29,32,# |
我们得到了两个归并段,r的大小减为2,而按照内部排序方法去得到初始归并段会得到三个。
文件经过置换-选择排序后,得到的是长度不等的初始归并段。下图为3路平衡归并的归并树。
归并方案不同,所得的归并树亦不同,树的带权路径长度亦不同。为了优化归并树的WPL,我们将二叉哈夫曼树推广到k叉哈夫曼树的情形。下图为三路归并时的最佳归并树。
http://t.csdn.cn/I7by1 →K叉哈夫曼树构造方法
当我们只有八个归并段时:我们设置了一个虚段才能去运用哈夫曼树的构造方法构造最佳归并树
K叉哈夫曼树是一个结点的度只有0或者k的树,这意味着: n0+nk=k*nk+1。
即 nk=(n0-1)/(k-1)
所以如果有n0个叶子结点,则n0-1要被k-1整除,我们才能用哈夫曼树的构造方法构造出最佳归并树,也就是说如果不能整除我们还要补上一些权值为零的虚段。
下面是求虚段个数的方法:
- 若(n0-1)%(k-1)=0,则正好不用设置虚段。
- 若(n0-1)%(k-1)=u,我们需要加上k-u-1个虚段,就可以建立归并树。