首页/数据结构/07-sorting/归并排序的非递归实现 🔗 在 Obsidian 中打开
数据结构 · 07-sorting

归并排序的非递归实现

重要度 ⭐⭐ 数据结构/排序
速查
非递归归并(Bottom-Up):从长度 1 的子表开始,归并长度倍增(1→2→4→…)直到 $\geq n$。每趟 O(n)、共 $\log n$ 趟 → O(n log n);稳定;空间 O(n)(辅助数组)、无递归栈。边界用 min 截断。

核心概念

递归 vs 非递归

递归(Top-Down)非递归(Bottom-Up)
过程先分裂到底,再逐层合并从单元素开始,倍增归并长度
栈空间O(log n) 递归栈 + O(n) 辅助无递归栈,仅 O(n) 辅助数组
时间O(n log n)O(n log n)
稳定性稳定稳定

算法思路:初始每个元素为长度 1 的有序子表;第 1 趟两两归并为长度 2;第 2 趟归并为长度 4;…… 直到归并长度 $\geq n$。

为什么适合外部排序?自底向上逐步归并,每趟只顺序扫描数据,契合磁盘顺序访问模式;且无递归栈,内存占用更可控。

伪代码

void MergeSort_NonRecursive(int A[], int n) {
    int *temp = (int*)malloc(n * sizeof(int));
    for (int len = 1; len < n; len *= 2) {   // 归并长度倍增
        for (int i = 0; i < n; i += 2*len) {  // 每次处理 2*len 个元素
            int left  = i;
            int mid   = min(i + len, n);      // 边界截断
            int right = min(i + 2*len, n);    // 边界截断
            Merge(A, temp, left, mid, right);
        }
    }
    free(temp);
}
  • 边界处理:mid、right 不能超过 n,用 min 截断。
  • 最后一组不完整:剩余不足 2·len 时 mid 或 right 截断到 n。
  • 辅助数组:每次 merge 需 O(n) 额外空间。

手算示例

例 1:$[8,4,5,7,1,3,6,2]$,$n=8$:

初始   [8,4,5,7,1,3,6,2]
len=1  [4,8] [5,7] [1,3] [2,6]  → 4,8,5,7,1,3,2,6
len=2  [4,5,7,8] [1,2,3,6]      → 4,5,7,8,1,2,3,6
len=4  [1,2,3,4,5,6,7,8]        → 完成(len=8 ≥ n)

例 2(不完整情况):$[3,1,4,1,5]$,$n=5$:len=1 → [1,3,1,4,5];len=2 → [1,1,3,4] 拼 [5];len=4 → 与 [5] 归并完成。中途残余子表直接拼接。

核心结论

情况时间复杂度空间复杂度
最好O(n log n)O(n)
平均O(n log n)O(n)
最坏O(n log n)O(n)

每趟归并 O(n),共 $\log n$ 趟 → O(n log n)。稳定排序(merge 时相等元素取前一个子表者)。

记忆卡片

非递归版时间复杂度与递归版一样吗?
一样:都是 O(n log n)。差别在空间:递归版多 O(log n) 栈。
核心循环结构?
双层:外层 len 倍增(1→…→≥n),内层两两归并相邻 len 子表。
子表长度不足怎么办?
min 截断 mid/right 到 n;残余直接拼接或并入归并。
稳定吗?适合外部排序吗?
稳定;自底向上顺序扫描、无递归栈,适合外部排序。

交互动画 · 自底向上归并

len = 1 橙=左子表,绿=右子表,合并后标记为有序块 80 41 52 73 14 35 66 27 初始:每个元素视为长度为 1 的有序子表
对 [8,4,5,7,1,3,6,2] 做非递归归并排序
点「下一趟」逐步倍增归并长度(len=1→2→4)
共 $\log_2 8 = 3$ 趟,每趟 O(n) → O(n log n)。

相关知识点

merge-sort quick-sort sorting-algorithm-comparison

↑ 以上为站内 HTML 相对链接(纯网页可浏览);本页右上「在 Obsidian 中打开」跳回源笔记。