K Way Merge And Loser Tree
多路归并
在外部归并排序中,若每次只归并两个归并段,归并趟数可能较多。把二路归并推广为 $k$ 路归并,就可以一次合并最多 $k$ 个归并段。
若有 $r$ 个初始归并段,采用 $k$ 路归并,归并趟数为:
$$
S=\lceil \log_k r\rceil
$$
$k$ 越大,归并趟数通常越少,磁盘 I/O 次数也随之减少。
多路归并的代价
多路归并不是只有好处。
| 影响 | 原因 |
|---|---|
| 输入缓冲区增多 | $k$ 路归并至少需要 $k$ 个输入缓冲区和一个输出缓冲区 |
| 选最小记录的比较次数增加 | 若朴素扫描 $k$ 个归并段首记录,每输出一个记录最多比较 $k-1$ 次 |
所以多路归并的矛盾是:
$$
k\uparrow\Rightarrow S\downarrow,\quad \text{磁盘 I/O 减少}
$$
但同时:
$$
k\uparrow\Rightarrow \text{选最小记录的内部比较代价增加}
$$
多路平衡归并
$k$ 路平衡归并满足两个条件:
- 每次最多把 $k$ 个归并段合并为一个新归并段。
- 若某一趟有 $m$ 个归并段参与归并,则这一趟后得到:
$$
\lceil m/k\rceil
$$
个新归并段。
不要把所有 $k$ 路归并都叫作 $k$ 路平衡归并
例如有 $8$ 个归并段,若做 $4$ 路平衡归并,一趟后应得到 $\lceil8/4\rceil=2$ 个新归并段。若人为分成 $4,2,2$ 三组,一趟后得到 $3$ 个新归并段,这不是 $4$ 路平衡归并。
败者树用于解决多路归并中“反复从 $k$ 个归并段首记录里选最小者”的问题。
败者树可以看作一棵完全二叉树:
- $k$ 个叶结点对应 $k$ 个归并段当前参加比较的记录;
- 除根结点外,其他内部结点记录本场比较中的败者来自哪个归并段;
- 根结点记录全局胜者来自哪个归并段;
- 胜者对应的叶结点就是当前所有归并段首记录中的最小者。
比较次数
第一次建立败者树时,所有叶结点都要参加比较。对于 $k$ 路归并:
$$
\text{建树比较次数}=k-1
$$
之后每输出一个最小记录,只需要让该归并段的新首记录沿原来的路径向上重新比赛,比较次数约为树高:
$$
\lceil \log_2 k\rceil
$$
这比每次朴素扫描 $k$ 个归并段的 $k-1$ 次比较少。
在归并中的使用
每次输出一个记录后:
- 记下冠军来自哪个归并段。
- 从该归并段取下一个记录,替换原冠军叶结点。
- 只沿该叶结点到根的路径重新比较。
- 新冠军就是下一次应输出的最小记录。
若某个归并段已经空,可把该叶结点的值视为 $+\infty$,这样它不会再成为冠军。
小结
| 考点 | 记法 |
|---|---|
| 多路归并作用 | 减少归并趟数,从而减少磁盘 I/O |
| 多路归并代价 | 需要更多输入缓冲区,朴素选择最小者比较次数多 |
| 朴素选择 | 每输出一个记录最多比较 $k-1$ 次 |
| 败者树建树 | $k-1$ 次比较 |
| 败者树调整 | 每输出一个记录约 $\lceil\log_2 k\rceil$ 次比较 |
| 败者树结点含义 | 非叶结点记败者,冠军继续上升 |
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 Elian's blog page!