归并排序(Merge Sort)

归并排序的重点不是“拆得很细”,而是“两个有序数组怎么线性合并成一个更大的有序数组”。

定义

归并排序是一种分治排序算法,采用”分而治之”的策略:将数组分成两半,分别排序,然后合并两个有序数组。

核心思路

  1. 分解:将数组递归地分成两半,直到每个子数组只有一个元素
  2. 解决:单个元素天然有序
  3. 合并:将两个有序子数组合并成一个有序数组
原数组: [38, 27, 43, 3, 9, 82, 10]

分解:
[38, 27, 43, 3, 9, 82, 10]
[38, 27, 43, 3] | [9, 82, 10]
[38, 27] [43, 3] | [9, 82] [10]
[38] [27] [43] [3] | [9] [82] [10]

合并:
[27, 38] [3, 43] | [9, 82] [10]
[3, 27, 38, 43] | [9, 10, 82]
[3, 9, 10, 27, 38, 43, 82]

🎞️ 分解与合并动画

(附件 merge-sort-split-merge.svg 未随站点发布)

看这个动画时要重点看“合并”阶段

真正把无序变成有序的,不是拆分动作,而是每次从两个有序子数组头部取较小值的过程。

为什么归并一定正确

归并排序的关键不变量是:

  • 递归返回时,左右子数组都已经各自有序。
  • 合并时,左右指针始终指向各自还没被放入结果中的最小元素。
  • 每次把两边较小的那个放进结果,结果数组前缀就始终保持有序。

复杂度分析

指标复杂度说明
最好时间O(n log n)总是递归log n层
平均时间O(n log n)同上
最坏时间O(n log n)性能稳定
空间复杂度O(n)需要额外数组存储
稳定性✅ 稳定合并时保持相对顺序

Go 代码

Go 实现

package main
 
import "fmt"
 
func mergeSort(arr []int) []int {
    if len(arr) <= 1 {
        return arr
    }
 
    mid := len(arr) / 2
    left := mergeSort(arr[:mid])
    right := mergeSort(arr[mid:])
 
    return merge(left, right)
}
 
func merge(left, right []int) []int {
    result := make([]int, 0, len(left)+len(right))
    i, j := 0, 0
 
    for i < len(left) && j < len(right) {
        if left[i] <= right[j] {
            result = append(result, left[i])
            i++
        } else {
            result = append(result, right[j])
            j++
        }
    }
 
    result = append(result, left[i:]...)
    result = append(result, right[j:]...)
 
    return result
}
 
func main() {
    arr := []int{38, 27, 43, 3, 9, 82, 10}
 
    fmt.Println("原始数组:", arr)
    sorted := mergeSort(arr)
    fmt.Println("排序后:", sorted)
}

算法优化

复用临时数组

如果每次 merge 都新建切片,常数开销会比较大。做题或工程实现里,可以预先申请一块临时数组,在递归过程中反复复用。

链表排序时优先考虑归并

数组上的归并有额外空间代价,但链表做归并通常很自然,因为拆链和合并链都比较顺手。

经典题目

基础应用

归并思想应用

合并操作

易错点

归并排序的核心 bug 通常都出在 merge 逻辑,而不是递归框架。

  • 合并结束后别忘了把剩余元素全部追加进去。
  • 想保稳定性时,比较条件要写成 <=,让左边相等元素先出。
  • 递归返回的必须是“已经排好序的子问题结果”。
  • 如果写原地版本,一定要特别小心覆盖顺序。

优缺点

优点

  • ✅ 时间复杂度稳定:保证 O(n log n)
  • ✅ 稳定排序:相等元素保持相对顺序
  • ✅ 性能可预测
  • ✅ 适合外部排序(处理大文件)
  • ✅ 易于并行化

缺点

  • ❌ 空间复杂度 O(n):需要额外数组
  • ❌ 对小数组性能不如插入排序
  • ❌ 不是原地排序
  • ❌ 递归开销

应用场景

  1. 稳定性要求:需要保持相等元素顺序
  2. 外部排序:数据量大,无法全部载入内存
  3. 链表排序:归并排序特别适合链表
  4. 并行排序:容易拆分成多个独立任务
  5. 时间保证:需要稳定的 O(n log n) 性能

归并排序的实际应用

归并排序 vs 快速排序

特性归并排序快速排序
平均时间O(n log n)O(n log n)
最坏时间O(n log n)O(n²)
空间O(n)O(log n)
稳定性稳定不稳定
实际性能较慢通常更快
适合场景链表、外部排序数组、一般排序

相关主题


返回:排序算法 | 算法学习导航