📘 归并排序(Merge Sort)
1. 问题定义
给定数组 (A[1...n]),要求输出:
A\[1\]≤A\[2\]≤⋯≤A\[n\]\]\[ A\[1\] \\le A\[2\] \\le \\dots \\le A\[n\] \]\[A\[1\]≤A\[2\]≤⋯≤A\[n\]
2. 核心思想
归并排序同样基于:
分治(Divide and Conquer)
但与快速排序的关键区别:
快排:先划分,再递归(Partition-based)
归并:先递归,再合并(Merge-based)
3. 核心术语
3.1 分治(Divide)
将数组递归拆分为:
A\[l..r\]→A\[l..mid\]+A\[mid+1..r\]\]\[ A\[l..r\] \\rightarrow A\[l..mid\] + A\[mid+1..r\] \]\[A\[l..r\]→A\[l..mid\]+A\[mid+1..r\]
直到每个子数组长度为 1
3.2 合并(Merge)
定义:
将两个已排序数组合并为一个有序数组
关键性质:
- 输入:两个有序序列
- 输出:一个有序序列
3.3 稳定性(Stability)
归并排序是:
✅ 稳定排序
因为合并时:
- 相等元素保持原顺序
3.4 非原地(Out-of-place)
归并排序需要:
- 额外数组辅助合并
空间复杂度为:
O(n)\]\[ O(n) \]\[O(n)
4. 算法结构
4.1 主函数
text
MergeSort(A, l, r):
if l >= r:
return
mid = (l + r) // 2
MergeSort(A, l, mid)
MergeSort(A, mid+1, r)
Merge(A, l, mid, r)
4.2 合并函数
text
Merge(A, l, mid, r):
创建临时数组 temp
i = l
j = mid + 1
while i ≤ mid and j ≤ r:
if A[i] ≤ A[j]:
temp.append(A[i])
i++
else:
temp.append(A[j])
j++
将剩余部分加入 temp
拷贝回 A[l..r]
5. 核心机制(教学重点)
5.1 归并排序的本质
不是"直接排序",而是:
先拆到最小,再逐层合并
5.2 为什么一定能排序成功?
因为:
每一层都满足:
- 子数组已经有序
- 合并操作保持有序
5.3 合并过程本质
本质是:
两个指针的线性扫描
6. 示例
初始数组
text
[6, 3, 8, 5]
拆分过程
text
[6,3,8,5]
→ [6,3] [8,5]
→ [6] [3] [8] [5]
合并过程
text
[6] + [3] → [3,6]
[8] + [5] → [5,8]
最终
text
[3,6] + [5,8] → [3,5,6,8]
7. 时间复杂度
7.1 递推式
T(n)=2T(n/2)+O(n)\]\[ T(n) = 2T(n/2) + O(n) \]\[T(n)=2T(n/2)+O(n)
7.2 求解
T(n)=O(nlogn)\]\[ T(n) = O(n \\log n) \]\[T(n)=O(nlogn)
7.3 关键理解
- 每一层合并成本:(O(n))(O(n))(O(n))
- 层数:(logn)(\log n)(logn)
8. 空间复杂度
需要辅助数组:
O(n)\]\[ O(n) \]\[O(n)
9. 与快速排序对比(重点)
| 项目 | 快速排序 | 归并排序 |
|---|---|---|
| 核心操作 | partition | merge |
| 时间复杂度 | 平均 (n log n) | 始终 (n log n) |
| 最坏情况 | (n^2) | (n log n) |
| 空间 | 原地 | 额外 (n) |
| 稳定性 | ❌ | ✅ |
10. 常见错误
❌ 错误1:认为 merge 很复杂
👉 本质只是"双指针"
❌ 错误2:忘记处理剩余部分
必须:
text
while i ≤ mid
while j ≤ r
❌ 错误3:边界写错
特别是:
text
mid + 1
11. 关键结论
- 归并排序核心是 merge
- 时间复杂度稳定 (nlogn)(n log n)(nlogn)
- 空间换时间
- 适用于大规模稳定排序
12. 最小教学总结
归并排序不是"在排",而是:
把问题拆小 → 再把有序结果合起来