数据结构——外部排序

一、外部排序

1.1 基本思想

有时,待排序的文件很大,计算机内存不能容纳整个文件,这时候对文件就不能单纯的使用内部排序了,还得采取一些其他的策略。因此需要将待排序的记录存储到外存上,排序时再将数据一部分一部分地调用内存进行排序,在排序过程中需要多次进行内存和外存之间地交换,这种排序方法就成为外部排序。

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

外部排序常采用的排序方法是归并排序,这种归并方法由两个不同的阶段组成:

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

1.2 图解

(1)初始状态
在这里插入图片描述

(2)将 ( 36 , 8 , 26 ) (36,8,26) (36,8,26) ( 42 , 9 , 48 ) (42,9,48) (42,9,48)分别存入输入缓冲区 1 1 1和输入缓冲区 2 2 2
在这里插入图片描述

(3)将输入缓冲区 1 1 1和输入缓冲区 2 2 2的数据进行递增排序
在这里插入图片描述
(4)将输入缓冲区 1 1 1和输入缓冲区 2 2 2的数据通过输出缓冲区逐一写入外存,形成一个有序归并段
在这里插入图片描述
(5)对于 ( 1 , 37 , 25 ) (1,37,25) (1,37,25) ( 45 , 27 , 28 ) (45,27,28) (45,27,28)进行上述同样的操作后得到的结果:
在这里插入图片描述

(6) 对剩余 12 12 12块内存依次进行上述操作,总共需要进行 16 16 16次读操作和 16 16 16次写操作,得到初始归并段。
在这里插入图片描述

(7)第一次归并:读入归并段 1 1 1和归并段 2 2 2中的第一块磁盘(相对最小),进行排序:
在这里插入图片描述

(8)依次找出这两个输入缓冲区中最小元素,并将其移动到输出缓冲区中,当输出缓冲区满,则写入外存 ( 1 , 8 , 9 ) (1,8,9) (1,8,9)
在这里插入图片描述

(9)继续找出这剩余元素中的最小元素,直到某一个缓冲区中空,则读入其所属归并段的后一个内存块的数据,并继续进行上述操作。直到两个缓冲区都空,且归并段 1 1 1和归并段 2 2 2中的元素全部读入内存,此时归并段 1 1 1和归并段 2 2 2就得到了一个有序的递增序列。

当输入缓冲区空了时:
在这里插入图片描述

输入归并段 1 1 1的第二块内存
在这里插入图片描述
排序完成,归并段 1 1 1和归并段 2 2 2递增有序
在这里插入图片描述
(10)对剩余的六个归并段进行上述操作,八个归并段→四个归并段
在这里插入图片描述
(11)第二次归并:继续采用此方法依次取出归并段 1 1 1和归并段 2 2 2(归并段 1 1 1为八个归并段时的归并段 1 1 1和归并段 2 2 2,归并段 2 2 2为八个归并段时的归并段 3 3 3和归并段 4 4 4)的各个块进行排序操作:四个归并段→两个归并段。
在这里插入图片描述
(12)第三次归并:继续排序归并段 1 、 2 1、2 12,形成最后的有序递增序列
在这里插入图片描述

1.3 外部排序的开销

总时间开销 = 内部排序所需时间 + 内部归并所需时间 + 外部读写所需时间

1.4 优化:多路平衡归并

若改用 4 4 4路归并排序,则只需 2 2 2趟归并,外部排序时的总读/写次数便减至 32 × 2 + 32 = 96 32×2+ 32= 96 32×2+32=96。因此,增大归并路数,可减少归并趟数,进而减少总的磁盘 I / O I/O I/O次数。

一般地,对 r r r个初始归并段,做 k k k路平衡归并,归并树可用严格 k k k叉树(即只有度为 k k k与度为 0 0 0的结点的 k k k叉树)来表示。第一趟可将 r r r个初始归并段归并为 ⌈ r / k ⌉ ⌈r/k⌉ r/k个归并段,以后每趟归并将 m m m个归并段归并成 ⌈ m / k ⌉ ⌈m/k⌉ m/k个归并段,直至最后形成一个大的归并段为止。 树的高度 − 1 = ⌈ l o g k r ⌉ = 归并趟数 S 树的高度-1=⌈log_kr⌉=归并趟数S 树的高度1=logkr=归并趟数S。可见,只要增大归并路数 k k k,或减少初始归并段个数 r r r,都能减少归并趟数 S S S,进而减少读写磁盘的次数,达到提高外部排序速度的目的。

二、败者树

为了使内部归并不受 k k k的增大的影响,引入了败者树。败者树是属性选择排序的一种变体,可视为一棵完全二叉树。

  1. 将每个归并段的第一个元素作为叶子结点加入败者树中
    在这里插入图片描述

  2. 从左至右、从上往下的更新分支节点的信息:判断其左右子树的大小,除了根节点(最上面那个结点)记录冠军来自哪个归并段外,其余各分支节点记录的是失败者来自哪个归并段。
    在这里插入图片描述

  3. 取出最小的元素 1 1 1后,从其所属的归并段中取出下一个元素 6 6 6,依次与从叶子结点到根节点的各个结点所记录的败者信息进行对比。
    在这里插入图片描述
    引进败者树后,选出最小的关键字,仅需 l o g 2 k log_2k log2k次比较(向上取整)。

可见,使用败者树后,内部归并的比较次数与 k k k无关了。因此,只要内存空间允许,增大归并路数 k k k将有效地减少归并树的高度,从而减少 I O IO IO次数,提高外部排序的速度。

值得说明的是,归并路数k并不是越大越好。归并路数 k k k增大时,相应地需要增加输入缓冲区的个数。若可供使用的内存空间不变,势必要减少每个输入缓冲区的容量,使得内存、外存交换数据的次数增大。当 k k k值过大时,虽然归并趟数会减少,但读写外存的次数仍会增加。

三、置换选择排序

使用选择置换排序,可以让每个初始段的长度不再受限于内存工作区大小,设内存工作区最多容纳 w w w个数据:

  1. 将待排序文件 F I FI FI输入 w w w个数据到内存工作区 W A WA WA
  2. 选择 W A WA WA中关键字最小的数据,输出到 F O FO FO中,并且用 M I N MIN MIN记录该最小关键字
  3. F I FI FI不空,则从 F I FI FI中继续输入文件到 W A WA WA
  4. W A WA WA中选出比 M I N MIN MIN更大的关键字的数据,输出并更新此最小关键字作为新 M I N MIN MIN
  5. 重复2~4直到 W A WA WA中的每个关键字都> M I N MIN MIN为止,由此得到一个新的归并段
  6. 重复2~5,直到 W A WA WA空,得到全部初始归并段

(1)初始状态
在这里插入图片描述
(2)将 ( 4 , 6 , 9 ) (4,6,9) (4,6,9)放入到内存工作区中,此时输出 4 4 4并修改 M I N = 4 MIN=4 MIN=4
在这里插入图片描述
(3)加入 7 7 7,选择最小元素 6 6 6,输出 6 6 6并修改 M I N = 6 MIN=6 MIN=6
在这里插入图片描述
(4)重复上述步骤,当放入 10 10 10时,此时 M I N = 13 MIN=13 MIN=13 10 10 10不能输出
在这里插入图片描述
选择第二小的元素 14 14 14输出,并修改 M I N = 14 MIN=14 MIN=14
在这里插入图片描述
(5)继续上面的步骤,当输出 30 30 30后,此时 M I N = 30 MIN=30 MIN=30,将下一个文件块放入到内存工作区中,此时内存工作区的所有元素都小于 M I N MIN MIN,因此第一个归并区到上一个输出元素为止 ( 4 、 6 、 7 、 9 、 11 、 13 、 14 、 16 、 22 、 30 ) (4、6、7、9、11、13、14、16、22、30) (4679111314162230)
在这里插入图片描述
(6)继续上述步骤,将新来的元素放到归并段 2 2 2中,此时内存工作区中最小的元素为 2 2 2,输出 2 2 2并将 M I N MIN MIN修改为 2 2 2。当输出到 36 36 36时,内存工作区中的所有元素都小于 M I N MIN MIN,因此第二个归并区到上一个输出元素为止 ( 2 、 3 、 10 、 17 、 19 、 20 、 23 、 36 ) (2、3、10、17、19、20、23、36) (23101719202336)
在这里插入图片描述
(7)继续上述步骤,将新来的元素放到归并段 3 3 3中,此时内存工作区中最小的元素为 1 1 1,输出 1 1 1并将 M I N MIN MIN修改为 1 1 1。直到所有元素都输出完毕之后,第三个归并区为 ( 1 、 5 、 12 、 18 、 21 、 39 ) (1、5、12、18、21、39) (1512182139)
在这里插入图片描述

四、最佳归并树

  1. 性质和构造完全相同于哈弗曼树
  2. 与哈弗曼树的区别:
    • k k k叉树,其中 k > 2 k > 2 k>2时:需要判断是否能满足构造完全 k k k叉树,若不满足,则需要添加长度为 0 0 0的“虚段”
      • ( 初始归并段数量 − 1 ) % ( k − 1 ) = 0 (初始归并段数量 - 1) \% (k - 1) = 0 (初始归并段数量1)%(k1)=0,则能构成完全 k k k叉树
      • ( 初始归并段数量 − 1 ) % ( k − 1 ) = u ≠ 0 (初始归并段数量 - 1) \% (k - 1)= u ≠ 0 (初始归并段数量1)%(k1)=u=0,则说明需要添加 ( k − 1 ) − u (k - 1)-u (k1)u个虚段才能构成完全二叉树

参考文章

https://blog.csdn.net/JiangNan_1002/article/details/124306250

  • 9
    点赞
  • 43
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值