外部排序

目录

外部排序的操作方法

外部排序的时间优化

①多路平衡归并与败者树

②置换选择排序与最佳归并树


 

外部排序的操作方法

对于记录很多的文件,无法将整个文件全部复制进内存中再进行排序。因此,只需待排序列的记录储存在外存中,排序时再把数据一部分一部分地调入内存进行排序,在排序过程中需要多次进行内存和外存之间的交换。这种排序方法就称为外部排序

外存信息的存取:文件通常是,按块存储在磁盘上,操作系统也是按块对磁盘上的信息进行读写的。因为磁盘读/写的机械动作所需的时间远远超过内存运算的时间,因此在外部排序过程中的时间代价注意考虑访问磁盘的次数,即I/O次数

外部排序通常采用归并排序法。它包括两个独立的阶段:

  1. 根据内存缓冲区的大小,将外存上的文件分成若干长度为 L 的子文件或,依次读入内存并利用内部排序方法对它们进行排序,并将排序后的有序子文件重新写回外存,称这些有序子文件为归并段顺串。
  2. 对这些归并段进行逐趟归并,并使归并段逐渐由小至大,直至得到整个有序文件为止。

对归并段进行逐趟归并的具体方法:我们在内存中开辟两个输入缓存区和一个输出缓存区

  1. 输入缓冲区:分别读入外存的两个归并段,然后归并它们,将结果放在输出缓存区。
  2. 输出缓冲区:存放归并的结果,但显然没法完全存放归并段的归并结果,因此需要在存满时再输出回磁盘。

fc2ca1e367df4becbe550812b2bf6020.png

 由于我们将两个为输入缓冲区大小的归并段归并成一个长度更长的归并段放在外存,之后输入缓冲区读这个更长的归并段时不能一次读完,只需先读入缓冲区大小(空了立马继续读入)即可,不影响内存中的归并操作。

外部排序的时间优化

外部排序时间开销=读写外存的时间+内部排序(初始生成归并段)所需时间+内部归并所需时间。其中读写外存的时间占比大的多,所以我们优化外部排序主要考虑减少读写外存时间。

而进行一次外存读写的时间均值是由外存设备决定的,所以我们优化外部排序需要减少外存信息的读写次数d

①多路平衡归并与败者树

 作用:减少归并趟数与优化多路归并的选择问题 

3b5a311d25b345fb82ac445b47fcc686.png

 

上图这是一个2路平衡归并,我们可以看出一共有四趟排好了整个大文件,而每一趟基本都要把所有记录都读/写一遍。对外存上信息的读/写是以“物理块”为单位的。假设每个物理块可以容纳200个记录,而待排文件有10000个记录,那么我们一趟归并就要“读”50次和“写”50次。四趟共400次加上初始生成归并段时进行的一趟读/写100次共计500次。

K路平衡归并:①最多只能有k个归并段归并成一个。

②每一趟归并中,若共有m个归并段参与归并,则经过这一趟处理得到 ⌈ m/k ⌉个新的归并段。

 0069eda7055c47a78e33e8f155c1e0ff.png

 上图是一个5路平衡归并,我们可以看出进行了二趟归并就排好了整个大文件,则共计读写次数为2*100+100=300次。一般情况下,      对r个初始归并段进行k路平衡归并时,归并的趟数:

                            S=⌈gif.latex?%5Clog_%7Bk%7Dr ⌉

可见,外存信息读次数d与归并趟数s有关,若增加k(路数)或减少r(归并段个数)便能减少趟数s。

多路归并带来的负面影响:①K路归并时,需要开辟k个输入缓存区,内存开销增加。

②每挑选一个关键字需要对比关键字k-1次,内部归并所需时间增加。(可用败者树优化)

做内部归并时,每趟归并n个元素需要做(n-1)(k-1)次比较,S趟归并总共需要的比较此数为

 S(n-1)(k-1)=⌈gif.latex?%5Clog_%7Bk%7Dr ⌉(n-1)(k-1)=⌈gif.latex?%5Clog_%7B2%7Dr ⌉(n-1)(k-1)/⌈ gif.latex?%5Clog_%7B2%7Dk

 其中,(k-1)/⌈ gif.latex?%5Clog_%7B2%7Dk⌉随k的增长而增长,败者树使内部排序不受k增长的影响。

败者树可视为一棵完全二叉树。完全二叉树的根结点的上方是“冠军”结点。

k个叶结点分别存放k个归并段在归并过程中当前参加比较的记录,

k-1个非终端结点用来记忆左右孩子结点中的“失败者”所在的归并段,而让胜者继续往上比较,一直到冠军结点。

(我们知道对于任意一棵二叉树,叶子结点数为gif.latex?n_%7B0%7D,度为2的结点数为gif.latex?n_%7B2%7D,则gif.latex?n_%7B0%7D=gif.latex?n_%7B2%7D+1。)

这样二叉树的所有非终端结点都记录“失败者”所在的归并段,“冠军”结点记录选择过的结点所在的归并段。

39e124ce41404330bb0d9341cacd7f58.png

 可见,使用败者树后,在k个记录中选择最小的关键字,最多需要 ⌈ gif.latex?%5Clog_%7B2%7Dk⌉次比较。总的比较次数可写为                                                      S(n-1)(k-1)=     ⌈gif.latex?%5Clog_%7Bk%7Dr ⌉ × (n-1) × ⌈ gif.latex?%5Clog_%7B2%7Dk⌉          =       ⌈gif.latex?%5Clog_%7B2%7Dr ⌉ (n-1)

使用败者树后,内部归并的比较次数与K无关了,只要内存空间允许,增大归并路数将有效减少归并树的高度,从而减少I/O次数。(k不能过大到减少输入缓冲区的容量,否则会增大r)。

②置换选择排序与最佳归并树

  作用:减少初始归并段个数与组织各归并段的归并顺序 

S=⌈gif.latex?%5Clog_%7Bk%7Dr ⌉ 可见,减少归并段的个数r是我们减少s的另一个途径。r= ⌈n/l ⌉。其中n是整个文件的记录数,l是初始归并段中的记录数。我们之前得到的初始归并段是在内存工作区中进行内部排序得到的,l取决于内存工作站的大小。若要减少r,则要增加l,则要探索新的排序方法。

置换-选择排序的特点是:在整个排序(得到初始归并段)过程中,选择最小(或最大)关键字和输入、输出交叉或平行进行。

置换-选择排序的操作过程为:

设内存工作区WA可容纳w个记录。外存中初始归并段输出文件为FO,外存中的初始待排文件为FI

  1. 从待排文件FI中输入w个记录到工作区WA。
  2. 从工作区WA中选出其中关键字取最小值的记录,记为MINMAX记录。
  3. 将MINMAX记录输出到外存的FO中。
  4. 若FI不空,则从FI输入下一个记录到工作区WA中。
  5. 从WA中所有关键字不比MINMAX记录小的关键字中选出最小的关键字,并作为新的MINMAX记录。
  6. 重复3~5,直到在WA中选不出新的MINMAX记录为止,由此得到一个初始归并段,输出一个归并段的结束标志到FO中去。
  7. 重复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,2917,21,05 
10,12,56,32,2917,21,4405
12,56,32,2910,21,4405,17
56,32,2910,12,4405,17,21
32,2910,12,5605,17,21,44
2910,12,3205,17,21,44,56
2910,12,3205,17,21,44,56,#
 29,12,3210
 29,3210,12
 3210,12,29
  10,12,29,32
  10,12,29,32,#

 我们得到了两个归并段,r的大小减为2,而按照内部排序方法去得到初始归并段会得到三个。

文件经过置换-选择排序后,得到的是长度不等的初始归并段。下图为3路平衡归并的归并树。

ad6f717c57e14162a72eb5a8a94698fb.png

 归并方案不同,所得的归并树亦不同,树的带权路径长度亦不同。为了优化归并树的WPL,我们将二叉哈夫曼树推广到k叉哈夫曼树的情形。下图为三路归并时的最佳归并树。

b3f3cf57de734bab866566b03a4386f8.png

 http://t.csdn.cn/I7by1 →K叉哈夫曼树构造方法

当我们只有八个归并段时:我们设置了一个虚段才能去运用哈夫曼树的构造方法构造最佳归并树

1c0c7f2301654219a5c5cc5df2832a8f.png

 

K叉哈夫曼树是一个结点的度只有0或者k的树,这意味着: n0+nk=k*nk+1。

即 nk=(n0-1)/(k-1)

所以如果有n0个叶子结点,则n0-1要被k-1整除,我们才能用哈夫曼树的构造方法构造出最佳归并树,也就是说如果不能整除我们还要补上一些权值为零的虚段。

下面是求虚段个数的方法:

  1. 若(n0-1)%(k-1)=0,则正好不用设置虚段。
  2. 若(n0-1)%(k-1)=u,我们需要加上k-u-1个虚段,就可以建立归并树。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
外部排序是一种用于处理大型文件的排序算法,由于文件太大无法全部读入内存,因此需要将文件分割成多个部分,分别进行排序后再合并。下面是一种基于归并排序外部排序实现: 1. 首先将大文件分割成多个小文件,每个小文件的大小可以根据内存的大小来决定,一般选择小于内存容量的值。 2. 对每个小文件进行排序,可以使用内置的sort函数或其他快速排序算法。 3. 将排序后的小文件依次读入内存,进行归并排序,生成一个有序的大文件。 4. 重复步骤1-3,直到所有小文件都已经排序并合并成一个大文件。 下面是一个简单的Python实现: ```python import heapq import os def external_sort(input_file, output_file, chunk_size=1000000): """外部排序""" chunks = [] with open(input_file, 'rb') as f: while True: chunk = f.read(chunk_size) if not chunk: break chunk = list(map(int, chunk.split())) chunk.sort() chunks.append(chunk) with open(output_file, 'wb') as f: for item in heapq.merge(*chunks): f.write('{} '.format(item).encode()) # 删除临时文件 for chunk_file in chunks: os.remove(chunk_file) if __name__ == '__main__': external_sort('big_file.txt', 'sorted_file.txt') ``` 这个实现中,我们将大文件分割成大小为`chunk_size`的小文件,每个小文件进行排序后,再将它们依次读入内存,使用heapq.merge函数进行归并排序,最后生成一个有序的大文件。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

晴落

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值