🧮 一周一个算法 · 第 3 周 归并排序(Merge Sort)——分治思想的第一次登场 “Divide and Conquer” —— 分而治之,算法的灵魂。 归并排序不仅是高效排序算法的代表,更是理解递归、复杂度分析与算法分层结构 的最佳入口。
🧭 一、引言:如何让"排序"并行起来? 假设你要给一副 1000 张的扑克牌排序。 最直观的想法是一个人从头到尾慢慢排好(冒泡/插入)。 但如果你把 1000 张牌分成 4 份,四个人同时各自排好,再合并呢?
✅ 每人工作量变少; ✅ 最后只需合并 四份有序牌; ✅ 这就是归并排序的核心思想:
Divide(划分) → Conquer(解决) → Combine(合并) 
🧩 二、算法思想 🌱 思想概括: 将数组一分为二; 对左右两部分分别递归排序; 将两个有序子数组合并 为一个新的有序数组。 图示说明: 1 2 3 4 5 6 7 8 9 10 11 12 原数组:  分解:            ↓                      ↓             ↓        ↓           ↓       ↓             ↓        ↓           ↓       ↓                   ↓                ↓      
⚙️ 三、伪代码 1 2 3 4 5 6 7 procedure MergeSort(A, left, right):     if left >= right:         return     mid ← (left + right) / 2     MergeSort(A, left, mid)     MergeSort(A, mid + 1, right)     Merge(A, left, mid, right)
🔧 合并(Merge)操作 将两个有序子数组 [left..mid] 和 [mid+1..right] 合并成一个新的有序数组。
1 2 3 4 5 6 7 8 9 10 11 12 13 procedure Merge(A, left, mid, right):     L ← A[left..mid]     R ← A[mid+1..right]     i ← 0, j ← 0, k ← left     while i < len(L) and j < len(R):         if L[i] ≤ R[j]:             A[k] ← L[i]; i ← i + 1         else:             A[k] ← R[j]; j ← j + 1         k ← k + 1     // 拷贝剩余元素     while i < len(L): A[k++] ← L[i++]     while j < len(R): A[k++] ← R[j++]
💻 四、C++ 实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 #include  <iostream>  #include  <vector>  void  merge (std::vector<int >& arr, int  left, int  mid, int  right)   {     int  n1 = mid - left + 1 ;     int  n2 = right - mid;     std::vector<int > L (n1) , R (n2)  ;     for  (int  i = 0 ; i < n1; ++i)         L[i] = arr[left + i];     for  (int  j = 0 ; j < n2; ++j)         R[j] = arr[mid + 1  + j];     int  i = 0 , j = 0 , k = left;     while  (i < n1 && j < n2) {         if  (L[i] <= R[j]) {             arr[k++] = L[i++];         } else  {             arr[k++] = R[j++];         }     }     while  (i < n1) arr[k++] = L[i++];     while  (j < n2) arr[k++] = R[j++]; }void  mergeSort (std::vector<int >& arr, int  left, int  right)   {     if  (left >= right) return ;     int  mid = left + (right - left) / 2 ;     mergeSort (arr, left, mid);     mergeSort (arr, mid + 1 , right);     merge (arr, left, mid, right); }int  main ()   {     std::vector<int > arr = {38 , 27 , 43 , 3 , 9 , 82 , 10 };     mergeSort (arr, 0 , arr.size () - 1 );     std::cout << "排序结果:" ;     for  (int  num : arr) std::cout << num << " " ;     std::cout << std::endl; }
输出:
📈 五、复杂度分析(数学视角) 1️⃣ 时间复杂度(递归关系式) 归并排序的运行时间满足:
T ( n ) = 2 T ( n 2 ) + O ( n ) T(n) = 2T\left(\frac{n}{2}\right) + O(n) T ( n ) = 2 T ( 2 n  ) + O ( n ) 
解释:
2 T ( n / 2 ) 2T(n/2) 2 T ( n /2 )  :递归两次处理子数组;O ( n ) O(n) O ( n )  :合并操作。使用 主定理(Master Theorem) :
T ( n ) = a T ( n / b ) + f ( n ) T(n) = aT(n/b) + f(n) T ( n ) = a T ( n / b ) + f ( n ) 
其中 a = 2 a = 2 a = 2  , b = 2 b = 2 b = 2  , f ( n ) = n f(n) = n f ( n ) = n 
由于 n log  b a = n log  2 2 = n 1 n^{\log_b a} = n^{\log_2 2} = n^1 n l o g b  a = n l o g 2  2 = n 1  , 且 f ( n ) = O ( n ) f(n) = O(n) f ( n ) = O ( n )  , 满足第二种情况:
T ( n ) = O ( n log  n ) T(n) = O(n \log n) T ( n ) = O ( n log  n ) 
✅ 最终结果:
T ( n ) = O ( n log  n ) T(n) = O(n \log n) T ( n ) = O ( n log  n ) 
2️⃣ 空间复杂度 需要额外数组存放临时合并结果:
S ( n ) = O ( n ) S(n) = O(n) S ( n ) = O ( n ) 
3️⃣ 稳定性分析 ✅ 稳定:当 L [ i ] = R [ j ] L[i] = R[j] L [ i ] = R [ j ]   时优先拷贝左侧元素。
🔢 六、数学深入:为什么是 O ( n log  n ) O(n \log n) O ( n log  n )  ? 我们从递归树角度理解:
每一层的合并代价都是 n :
1 2 3 4 5 第0 层:1 个子问题,大小n  → 代价 n  第1 层:2 个子问题,大小n /2  → 代价 2 *(n /2 ) = n  第2 层:4 个子问题,大小n /4  → 代价 4 *(n /4 ) = n  ... 总层数 ≈ log ₂n 
总代价:
n + n + n + ⋯ + n = n log  2 n n + n + n + \cdots + n = n \log_2 n n + n + n + ⋯ + n = n log  2  n 
因此:
T ( n ) = O ( n log  n ) T(n) = O(n \log n) T ( n ) = O ( n log  n ) 
🚀 七、工程特性与实践优化 优化方向 思想 效果 小区间改用插入排序 当子数组长度 < 16,用插入排序更快 CPU Cache友好 原地合并优化 避免临时数组 减少空间开销(但实现复杂) 多线程归并 两个子问题并行 大幅提高多核性能 外部排序(磁盘归并) 适合大数据集 I/O 优化 
🧠 八、思考与练习 为什么归并排序在链表排序中表现非常优? (提示:合并链表可以 O ( 1 ) O(1) O ( 1 )   操作,无需移动内存)
若数组长度是 1024,递归深度是多少? (提示:log  2 1024 = 10 \log_2 1024 = 10 log  2  1024 = 10  )
请尝试实现 迭代版(非递归版)  归并排序。
你能在 merge() 函数中只用一个缓冲区完成左右合并吗?
🌱 九、延伸阅读 ✨ 十、总结 指标 说明 时间复杂度 O ( n log  n ) O(n \log n) O ( n log  n ) 空间复杂度 O ( n ) O(n) O ( n ) 稳定性 ✅ 稳定 是否原地排序 ❌ 否(需辅助数组) 适用场景 大数据、链表排序、外部排序 
“归并排序是算法的第一次哲学思考—— 把问题拆分成能解决的小问题,再组合回答案。”
🔜 下周预告(第 4 周) 
快速排序(Quick Sort)——“分治"遇上"随机”  我们将看到:相同的分治思想,如何在"期望复杂度 O ( n log  n ) O(n \log n) O ( n log  n )  "中跑得更快。