外部排序

2176 字
11 分钟
外部排序
Warning

含AI生成内容

外部排序#

当数据量大到内存放不下时,所有内部排序算法都失效了。外部排序的策略:先把数据分成小块在内存中排序后写回磁盘(生成初始归并段),再用多路归并合并成最终有序文件。核心瓶颈在于磁盘 I/O

基本流程#

  1. 生成初始归并段:将大文件分成若干段,每段读入内存用内部排序排好序后写回外存
  2. 多路归并:对所有归并段进行多趟归并,直到整个文件有序
文件(N条记录)
↓ 内部排序
[段1] [段2] ... [段m] ← m个初始归并段
↓ k路归并
[有序文件]

归并趟数#

设有 m 个初始归并段,采用 k 路归并:

S = ⌈log_k(m)⌉

减少归并趟数的两种途径:

策略方法效果
增大归并路数 k使用多路归并S 随 k 增大而减小
减少初始归并段数 m使用置换-选择排序m 减小,S 随之减小

总 I/O 次数 = S × 2 × ⌈N/B⌉,N 为记录总数,B 为磁盘块容量。

败者树#

作用#

在 k 路归并中加速”从 k 个元素中选最小值”的过程。

选最小值方式比较次数适用场景
直接比较k-1k 较小时
败者树⌈log₂k⌉k 较大时显著优于直接比较

工作原理#

  • 叶子结点存放 k 个归并段的当前元素
  • 内部结点记录”败者”(较大者)的来源编号
  • 根结点之上记录”冠军”(最小者)
  • 每次取走最小元素后,只需沿该路径调整,比较次数为 ⌈log₂k⌉
叶子层: b₀(5) b₁(12) b₂(3) b₃(9)
第1轮: 5 vs 12 → 败者=b₁ | 3 vs 9 → 败者=b₃
胜者=b₀(5) 胜者=b₂(3)
第2轮: 5 vs 3 → 败者=b₀, 胜者=b₂(3)
结果: 冠军=b₂(3)

调整时只需沿被替换叶子到根的路径比较,4 路归并只需 2 次比较。

置换-选择排序#

普通方法生成的初始归并段长度 = 内存工作区大小 M。置换-选择排序可以生成平均长度 2M 的初始归并段。

算法步骤:

  1. 从文件读入 M 条记录填满内存工作区 WA
  2. 从 WA 中选出最小的且不小于上一个输出记录的记录,输出到当前归并段
  3. 从文件读入下一条记录填补 WA 空位
  4. 重复直到 WA 中所有记录都小于上一个输出记录
  5. 当前归并段结束,开始生成下一个归并段

最佳归并树#

当各归并段长度不等时,归并顺序影响总 I/O 次数。用类似哈夫曼树的方法构造 k 叉最佳归并树,权值大的归并段靠近根(晚参与归并),总 I/O 次数最少。

虚段补充规则#

最佳归并树是严格 k 叉树(每个内部结点恰有 k 个孩子)。设初始归并段数为 m:

u = (m - 1) % (k - 1)

  • u = 0:无需补虚段
  • u ≠ 0:补充 (k - 1 - u) 个虚段(长度为 0)

真题示例(2019 统考):

120 个初始归并段,12 路归并: u = (120-1) % (12-1) = 119 % 11 = 9 需补充 (12-1)-9 = 2 个虚段

复杂度#

指标公式/值
归并趟数S = ⌈log_k(m)⌉
每趟 I/O2 × ⌈N/B⌉(一读一写)
败者树选最小值⌈log₂k⌉ 次比较
置换-选择平均段长2M

⚠️ 易错:外部排序的性能瓶颈是磁盘 I/O 次数,不是比较次数。减少 I/O 的关键是减少归并趟数。

⚠️ 易错:增大归并路数 k 时,直接比较需要 k-1 次,用败者树可优化到 log₂k 次。

考研高频考点#

  • ⭐ 归并趟数公式 S = ⌈log_k(m)⌉ 计算
  • ⭐ 增大 k 与减少 m 对趟数的影响
  • ⭐ 败者树的作用及比较次数 ⌈log₂k⌉
  • ⭐ 置换-选择排序生成初始归并段的过程
  • ⭐ 最佳归并树构造与虚段补充规则
  • 外部排序总 I/O 次数计算
  • 内部归并排序 vs 外部归并排序的区别

关联页面#

外部排序#

当数据量大到内存放不下时,所有内部排序算法都失效了。外部排序的策略:先把数据分成小块在内存中排序后写回磁盘(生成初始归并段),再用多路归并合并成最终有序文件。核心瓶颈在于磁盘 I/O

基本流程#

  1. 生成初始归并段:将大文件分成若干段,每段读入内存用内部排序排好序后写回外存
  2. 多路归并:对所有归并段进行多趟归并,直到整个文件有序
文件(N条记录)
↓ 内部排序
[段1] [段2] ... [段m] ← m个初始归并段
↓ k路归并
[有序文件]

归并趟数#

设有 m 个初始归并段,采用 k 路归并:

S = ⌈log_k(m)⌉

减少归并趟数的两种途径:

策略方法效果
增大归并路数 k使用多路归并S 随 k 增大而减小
减少初始归并段数 m使用置换-选择排序m 减小,S 随之减小

总 I/O 次数 = S × 2 × ⌈N/B⌉,N 为记录总数,B 为磁盘块容量。

败者树#

作用#

在 k 路归并中加速”从 k 个元素中选最小值”的过程。

选最小值方式比较次数适用场景
直接比较k-1k 较小时
败者树⌈log₂k⌉k 较大时显著优于直接比较

工作原理#

  • 叶子结点存放 k 个归并段的当前元素
  • 内部结点记录”败者”(较大者)的来源编号
  • 根结点之上记录”冠军”(最小者)
  • 每次取走最小元素后,只需沿该路径调整,比较次数为 ⌈log₂k⌉
叶子层: b₀(5) b₁(12) b₂(3) b₃(9)
第1轮: 5 vs 12 → 败者=b₁ | 3 vs 9 → 败者=b₃
胜者=b₀(5) 胜者=b₂(3)
第2轮: 5 vs 3 → 败者=b₀, 胜者=b₂(3)
结果: 冠军=b₂(3)

调整时只需沿被替换叶子到根的路径比较,4 路归并只需 2 次比较。

置换-选择排序#

普通方法生成的初始归并段长度 = 内存工作区大小 M。置换-选择排序可以生成平均长度 2M 的初始归并段。

算法步骤:

  1. 从文件读入 M 条记录填满内存工作区 WA
  2. 从 WA 中选出最小的且不小于上一个输出记录的记录,输出到当前归并段
  3. 从文件读入下一条记录填补 WA 空位
  4. 重复直到 WA 中所有记录都小于上一个输出记录
  5. 当前归并段结束,开始生成下一个归并段

最佳归并树#

当各归并段长度不等时,归并顺序影响总 I/O 次数。用类似哈夫曼树的方法构造 k 叉最佳归并树,权值大的归并段靠近根(晚参与归并),总 I/O 次数最少。

虚段补充规则#

最佳归并树是严格 k 叉树(每个内部结点恰有 k 个孩子)。设初始归并段数为 m:

u = (m - 1) % (k - 1)

  • u = 0:无需补虚段
  • u ≠ 0:补充 (k - 1 - u) 个虚段(长度为 0)

真题示例(2019 统考):

120 个初始归并段,12 路归并: u = (120-1) % (12-1) = 119 % 11 = 9 需补充 (12-1)-9 = 2 个虚段

复杂度#

指标公式/值
归并趟数S = ⌈log_k(m)⌉
每趟 I/O2 × ⌈N/B⌉(一读一写)
败者树选最小值⌈log₂k⌉ 次比较
置换-选择平均段长2M

⚠️ 易错:外部排序的性能瓶颈是磁盘 I/O 次数,不是比较次数。减少 I/O 的关键是减少归并趟数。

⚠️ 易错:增大归并路数 k 时,直接比较需要 k-1 次,用败者树可优化到 log₂k 次。

考研高频考点#

  • ⭐ 归并趟数公式 S = ⌈log_k(m)⌉ 计算
  • ⭐ 增大 k 与减少 m 对趟数的影响
  • ⭐ 败者树的作用及比较次数 ⌈log₂k⌉
  • ⭐ 置换-选择排序生成初始归并段的过程
  • ⭐ 最佳归并树构造与虚段补充规则
  • 外部排序总 I/O 次数计算
  • 内部归并排序 vs 外部归并排序的区别

关联页面#

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

外部排序
https://lingluoa.icu/posts/external-sort/
作者
lingluoa
发布于
2026-07-07
许可协议
CC BY-NC-SA 4.0

评论区

Profile Image of the Author
lingluoa
Hello, I'm lingluoa.
公告
欢迎来到我的博客!不定期更新中。
文章目录
标签
站点统计
文章
53
分类
9
标签
75
总字数
175,771
运行时长
0
最后活动
0 天前
站点信息
构建平台
ESA Pages
博客版本
Firefly v6.15.6
文章许可
CC BY-NC-SA 4.0