Merge Sort 归并排序原理和复杂度

FreeGuideOnline 最新 2026-07-10

新数组 = [] left指针 → 1, right指针 → 2 → 1 < 2 → 取 1,新数组 [1],left指针后移 left指针 → 4, right指针 → 2 → 4 > 2 → 取 2,新数组 [1,2],right指针后移 left指针 → 4, right指针 → 3 → 4 > 3 → 取 3,新数组 [1,2,3],right指针后移 left指针 → 4, right指针 → 5 → 4 < 5 → 取 4,新数组 [1,2,3,4],left指针后移 left指针 → 6, right指针 → 5 → 6 > 5 → 取 5,新数组 [1,2,3,4,5],right指针后移 right已空 → 将 left 剩余 [6] 直接追加 → 最终 [1,2,3,4,5,6]


---

## 递归过程全览(图文步骤示意)

假设我们要排序的数组为 `[38, 27, 43, 3, 9, 82, 10]`。

### 拆分阶段

```text
原始数组:          [38, 27, 43, 3, 9, 82, 10]
拆分 1:      [38, 27, 43, 3]       [9, 82, 10]
拆分 2:   [38, 27]    [43, 3]    [9, 82]   [10]
拆分 3: [38] [27]   [43] [3]   [9] [82]  [10]

当拆成单个元素时,每个子数组已经是有序的(长度为1自然有序)。

合并阶段(自底向上归并)

[38] [27]   → 合并成 [27, 38]
[43] [3]    → 合并成 [3, 43]
[27, 38] 与 [3, 43] → 合并成 [3, 27, 38, 43]

[9] [82]   → 合并成 [9, 82]
[9, 82] 与 [10] → 合并成 [9, 10, 82]

[3, 27, 38, 43] 与 [9, 10, 82] → 最终合并成 [3, 9, 10, 27, 38, 43, 82]

每次合并都利用“合并两个有序数组”的算法,保证合并后的数组依然有序。


时间复杂度分析

归并排序无论数组初始状态如何,都会执行完整的拆分与合并过程,因此其最好、最差和平均时间复杂度都是 O(n log n)

推导过程

  • 拆分:每次将数组对半分,直到长度为1,拆分次数与二叉树高度相同,深度为 log₂n
  • 合并:每一层递归中,所有子数组的合并操作会遍历整个数组中的每个元素,即每一层的总操作量为 O(n)。
  • 因此总复杂度 = 层数 × 每层工作量 = O(n log n)

递归树示例(n=8)

层数(拆分后)         每层合并总元素量
第0层: n=8            合并为大小为8的数组 → O(8)
第1层: 两个 n/2=4     两个合并各O(4) → 合计 O(8)
第2层: 四个 n/4=2     四个合并各O(2) → 合计 O(8)
第3层: 八个 n/8=1     长度为1无需合并

总层级为 log₂8 = 3 层(不含大小为1的层),每层 O(n),因此总共 O(3×8) = O(24),即 O(n log n)。


空间复杂度

归并排序需要额外的数组来存放合并过程中的有序结果,因此空间复杂度为 O(n)

  • 在合并两个子数组时,需要临时创建一个新数组来存储合并后的数据。这个临时数组长度等于两个子数组长度之和。
  • 递归过程中,同一时刻可能存在的临时数组总大小不会超过原数组长度(取决于实现方式,使用原地归并可以降低,但通常都不是原地的)。
  • 另外递归调用栈的深度为 O(log n),因此总空间 = O(n) + O(log n) = O(n)

这使归并排序成为非原地排序(out-of-place),对于内存敏感的大数据场景需要特别注意。


稳定性

归并排序是稳定排序

稳定性指相等元素的相对顺序在排序后保持不变。在合并两个有序数组时,如果两个指针指向的元素相等,我们优先取左边子数组的元素,这样就能保证相等元素的原始先后顺序。伪代码如下:

如果 left[i] <= right[j],取 left[i] (优先保留左边元素)
否则取 right[j]

正是这个 <= 保证了对左数组的优先处理,从而实现了稳定性。


优缺点与应用场景

优点

  • 时间复杂度稳定在 O(n log n),不受输入数据影响。
  • 稳定排序,适用于需要保持原始顺序的场景。
  • 特别适合链表排序,因为链表的合并操作不需要额外空间,空间复杂度可优化至 O(1)。
  • 适用于外部排序(数据太大无法全部装入内存),可分割成多个小文件排序后再多路归并。

缺点

  • 需要 O(n) 额外内存,对数组排序时内存开销大。
  • 相比快速排序,常数因子更大,实际运行速度可能稍慢。

伪代码实现

递归版本核心逻辑

函数 mergeSort(arr):
    如果 arr 长度 <= 1,直接返回 arr
    
    找到中点 mid = length // 2
    left = mergeSort(arr[0...mid-1])
    right = mergeSort(arr[mid...end])
    
    返回 merge(left, right)

函数 merge(left, right):
    创建空数组 result
    i = 0, j = 0
    当 i < len(left) 且 j < len(right):
        如果 left[i] <= right[j]:
            将 left[i] 加入 result
            i++
        否则:
            将 right[j] 加入 result
            j++
    
    // 将剩余元素加入
    将 left[i...] 全部加入 result
    将 right[j...] 全部加入 result
    
    返回 result