4.算法进阶——外部排序,如何用有限的内存对TB级数据进行排序

比如有这样一个基于真实场景的经典面试题:假设现在有 1TB 的任意文本,请问如何能将其中出现的单词按照字母序排列,得到一个新的文本?

这个问题的关键点其实在于,内存是有限的,而所谓外部排序的这个外:指的是外部存储
已知1GB的内存如果要处理1TB,至少要读1024次。
所以大体思路一定是:将文件分段,用常见内部排序方法进行排序完了之后,再合并。即思路上大体是归并排序

主要影响因素

1.内部排序
我们分成一段一段之后,主要目的是使得内存能够装下这一段数据
一般来说,快速排序在大部分场景下都是最快的

2.归并阶段
因为需要归并n多个段,此时的内存肯定无法装下超过一个段的内容。此时最大的时间消耗来自IO
内存的读写操作是很快的,但是外部磁盘中读写,可能比内存中要慢非常非常多。
4.算法进阶——外部排序,如何用有限的内存对TB级数据进行排序_第1张图片
我们看这个教程中的图片。每一次的归并,我们都得从外存中读。所以IO次数和归并层数成正比

3.如何降低归并层数——增加归并路数k
那就得每次多归并几段,上图这种每次归并两段,就是两路归并。如果我们使用k路归并,每一层都只有上一层的1/k。只需要Log Kn层即可。

4.增加k会导致什么问题
我们每次合并的成本肯定变大了,我们需要在k个元素中,每次找到最小的那个。这肯定比两个选一个代价大。

但是,很明显,选择k个元素中的最小元素,用小顶堆就能实现

而实际应用中,最广泛的方法是:

败者树

败者树算法来源于体育比赛。
4.算法进阶——外部排序,如何用有限的内存对TB级数据进行排序_第2张图片
叶子节点两两比赛,父节点存储小的那个,这样最顶上面就是最小的元素。
和堆一样,当选择出第一个数之后,将其拿走,我们要找下一个数。
这里有两种情况:
1.正常来说我们需要找到另一个元素来替代1(即某一路元素命中,但是该路元素没有被取完)
2.取走之后不再添加新的元素(即该数是该路中最后一个元素)

不论那种情况,都需要重新比赛,只不过第二种情况,我们添加一个无限大的数,使其不可能获胜即可。

更简便的方法,我们只需要将新加入的数与原来的亚军相比较 就能知道第二次的冠军是谁。

总结

回答最开始的问题:

  1. 先用内部排序算法,尽可能多的加载1TB文件,而后将其分成n个有序段
  2. 反复执行:将k个有序段合并成一个顺序段(借助败者树)。直到所有顺序段都被归并到一起。
  3. 注意每次从败者树中取元素,不一定产生IO,因为操作系统读取文件按页读取并缓存,大概率直接命中了cache

败者树与小顶堆的区别

第一,队列长度固定
第二,插入位置在叶节点。败者树是为多路归并量身定做的,效率应该更高,堆原来为了维护满二叉树的工作在多路归并中是不需要的。
败者树、胜者树的主要区别在于内存的访问次数;胜者树是和兄弟节点做比较,所以取出父节点还要再取一次子节点;访存次数更多。败者树则解决了这个问题。而堆必然每次都需要比较两次

你可能感兴趣的:(算法进阶实战,算法,排序算法,面试)