数据结构学习笔记之排序·外部排序

二、外部排序

1、基本概念

  • 外部排序指待排序的内容庞大,内存一次装不下,需要将内容存放在外存的排序。外部排序进行排序事,将内容一部分一部分地调入内存进行排序,因此需要在外存和内存之间进行多次交换。
  • 因而,一个好的外部排序算法应当可以做到尽可能少的进行内外存的交换,因为外存的读写速度显然比较慢,如果内外存交换次数太多就会影响到程序运行效率。简单来说,考量一个外部排序算法的时间复杂度,主要看它的 I/O 次数。

2、外部排序的方法

  • 外部排序通常采用归并排序,分为两个阶段:
    ① 根据缓冲区大小对待排序的文件进行划分,划分成若干长度为 l 的子文件,然后依次读入内存利用内部排序算法进行排序,将排序后的有序子文件写回外存,这些有序子文件称为归并段顺串
    ② 对归并段进行逐趟归并直至得到整个有序文件。
  • 例如一个含有 1000 个记录的文件,进行二路平衡归并:
    数据结构学习笔记之排序·外部排序
  • 一般外部排序所需的时间由内部排序所需时间、外存信息读写时间和内部归并所需时间三者之和。
  • 为了减少 I/O 次数,一般对 r 个初始归并段做 k 路平衡归并,归并树可用 k 叉树表示,此时树的高度为 logkr 的向上取整,树的高度也就是归并趟数。因此,增大归并路数 k,或减少初始归并段个数 r,都可以减少归并趟数,进而减少 I/O 次数。

3、归并路数 k 的影响

  • 归并路数 k 的增加虽然能减少 I/O 次数,但是会增加内部排序时间。k 个元素的选择最小元素需要 k-1 次比较,每趟归并 n 个元素需要 (n-1)(k-1) 次比较,故 S 趟归并总共需要比较次数为:
    数据结构学习笔记之排序·外部排序
  • 显然式子中存在与 k 相关的因式,且该因式会随 k 的增大而增大。为了避免减少 I/O 次数带来的时间效益被内部排序的时间增加所抵消,不能选择一般的内部排序算法。

4、败者树

  • 为了使内部归并不受归并路数 k 的影响,引入了败者树,这是一种树形选择排序的变体,可视为完全二叉树。
  • k 个叶节点存放 k 个归并段在归并过程中当前参加比较的记录,内部节点用来记忆左右子树中的“失败者”,而让胜者继续比较直到根节点。所谓胜者和败者,根据是否满足条件而定,若是增序,则胜者是关键值小者,败者是关键值大者,于是根节点就是最小值。
    数据结构学习笔记之排序·外部排序
  • (a)所示,b3与b4比较,b4是败者,将段号4写入父结点Is[4]。b1与b2比较,b2是败者,将段号2写入ls[3]。b3与b4的胜者b3与b0比较。b0是败者,将段号0写入ls[2]。最后两个胜者b3与b1比较,b1是败者,将段号1写入ls[1]。而将胜者b3的段号3写入Is[0]。此时,根结点Is[]0所指的段的关键字最小。b3中的6输出后,将下一关键字填入b3,继续比较。
  • 此时,k 路归并败者树深度为 log2k 向上取整,所以比较次数为:
    数据结构学习笔记之排序·外部排序
  • 此时,显而易见的是归并路数 k 与最终比较次数无关了,也即归并路数的增大可以有效提高时间效率了。
  • 但 k 的大小不宜太大,太大会影响到输入缓冲区的大小,因为内存的容量一定是有限的;超过内存的承受限度,k 的增大反而增加 I/O 次数。

5、置换-选择排序

  • 置换-选择排序,是一种专门用于生成外部排序的归并排序算法的初始归并段的算法,其主要过程如下:
    设待排序文件为 FI,初始归并段输出文件为 FO,内存工作区为 WA,WA 和 FO 初始皆为空,WA 容量为 w。
    ① 从 FI 输入 w 个记录到 WA;
    ② 从 WA 中选出最小值的记录,记为 MINIMAX 记录;
    ③ 将 MINIMAX 记录输出到 FO 中;
    ④ 若 FI 不为空,则输入下一个记录到 WA;
    ⑤ 从 WA 中所有比 MINIMAX 记录都大的记录中选出最小记录,作为新的 MINIMAX 记录
    ⑥ 重复前三步,直到 WA 中选不出新的 MINIMAX 记录为止,由此得到一个初始归并段,输出一个归并段结束的标志到 FO 中。
    ⑦ 重复②~⑥,直到 WA 为空。由此得到全部的初始归并段。
    数据结构学习笔记之排序·外部排序
  • 在 WA 中选择 MINIMAX 用败者树算法。

6、使用哈夫曼算法得出最佳归并树

  • 对经过置换-选择排序得到的长度不一的初始归并段,采用哈夫曼算法原理,让记录数少的初始归并段先归并,记录数多的晚归并,从而得到总 I/O 次数最少的最佳归并树。
  • 进行归并时,若初始归并段不足以构成一棵严格的 k 叉树,需要添加长度为 0 的“虚段”,并且这个“虚段”构成的叶子放置在离树根最远的地方。
  • 例如 8 个初始归并段长度分别为:9,12,18,3,17,2,6,24;对该初始归并段进行 3 路平衡归并,其正确的最佳归并树为:
    数据结构学习笔记之排序·外部排序
  • 具体要添加多少“虚段”,可由如下推理:
    设度为 0 的节点有 n0=n 个,度为 k 的节点为 nk 个,则对严格 k 叉树有 n0=(k-1)nk+1,即 nk=(n0-1)/(k-1)。
    ① (n0-1)%(k-1)==0 时,正好可以构成严格 k 叉树,此时无需添加虚段;
    ② (n0-1)%(k-1)= 0 ≠ 0,则在 n0 个叶节点中,有 u 个多余,不能包含在 k 叉归并树上,此时需要加上 k-u-1 个“虚段”来帮助构成归并树。