Menu
CoddyTech

归并排序

最后更新

归并排序是一种分治算法。它递归地把数组一分为二,直到每一段只剩一个元素(单个元素天然有序),然后按顺序把这些片段合并回去。合并时用两个指针遍历两个有序子数组,每次把较小的那个队首元素复制到结果中。点击上方的“播放”,观看数组如何经过一次次合并重新拼好。

因为每次都对半拆分,归并排序在任何情况下都是 O(n log n):最坏情况和最好情况一样好。代价是合并时需要一个临时缓冲区,占用 O(n) 的额外空间。

时间复杂度与空间复杂度

情况复杂度说明
最好情况O(n log n)始终对半拆分输入
平均情况O(n log n)随机顺序
最坏情况O(n log n)有保证,不存在糟糕的输入
空间O(n)合并用的临时缓冲区
稳定性稳定合并时相等元素优先取左边

分步说明

步骤发生了什么
1如果区间只有 0 或 1 个元素,它已经有序。
2把区间拆成两半。
3对左半部分递归地进行归并排序。
4对右半部分递归地进行归并排序。
5用两个指针合并两个有序的半区。

实例演示

对 [5, 2, 4, 1] 排序:

轮次数组操作
拆分[5, 2] | [4, 1]把数组分成两半
拆分[5] [2] | [4] [1]继续拆分,直到每一段只有一个元素
合并[2, 5] | [1, 4]把 [5]、[2] 合并成 [2, 5],把 [4]、[1] 合并成 [1, 4]
合并[1, 2, 4, 5]合并 [2, 5] 和 [1, 4]:依次取出 1、2、4、5
完成[1, 2, 4, 5]数组完全有序

什么时候用归并排序

适合使用不适合使用
你需要有保证的 O(n log n) 最坏情况内存紧张,无法接受 O(n) 的额外空间
稳定性很重要(相等的键保持原有顺序)数组很小,这时插入排序更快
你要对链表排序必须原地排序
数据太大,内存放不下(外部排序)缓存局部性起决定作用,快速排序的原地遍历更占优

Merge Sort 代码

简洁、可运行的 Merge Sort 实现,提供 Python, JavaScript, Java, C++, C, Pseudocode 版本。选择一种语言,复制代码,或在 Coddy 在线编辑器中直接打开。

Merge Sort 的 Python 代码

Python
1def merge_sort(a):2    if len(a) <= 1:3        return a4    mid = len(a) // 25    left = merge_sort(a[:mid])6    right = merge_sort(a[mid:])7    return merge(left, right)8
9
10def merge(left, right):11    out = []12    i = j = 013    while i < len(left) and j < len(right):14        if left[i] <= right[j]:15            out.append(left[i])16            i += 117        else:18            out.append(right[j])19            j += 120    out.extend(left[i:])21    out.extend(right[j:])22    return out23
24
25nums = [38, 27, 43, 3, 9, 82, 10]26print("Before:", nums)27print("After: ", merge_sort(nums))
在 Python 在线编辑器中运行这段代码

归并排序常见问题

归并排序的时间复杂度是多少?
归并排序在最好、平均和最坏情况下都是 O(n log n),因为它总是把数组对半拆分。合并缓冲区需要 O(n) 的额外空间。
归并排序是稳定的吗?

是的,只要合并时遇到相等元素优先从左半部分取。这样相等元素就能保持原来的相对顺序,这也是归并排序常被用作稳定排序的原因。

为什么选归并排序而不是快速排序?
归并排序即使面对刻意构造的输入也能保证 O(n log n),而且是稳定的,而快速排序可能退化到 O(n²)。对链表排序和外部排序时也更适合用归并排序。缺点是需要 O(n) 的额外内存。
归并排序和快速排序有什么区别?
两者都是分治排序,但快速排序围绕基准值分区,原地排序,只需 O(log n) 的栈空间;归并排序不看数据直接对半拆分,再借助 O(n) 的缓冲区合并。由于缓存局部性更好,快速排序实际上通常更快,但归并排序有保证的 O(n log n) 最坏情况,而且是稳定的。
实际开发中什么时候该用归并排序?
当你需要有 O(n log n) 保证的稳定排序时,对链表排序时(它不需要随机访问),或者对大到内存放不下的数据做外部排序时,就用归并排序。内存紧张时要避免使用,因为它需要 O(n) 的额外空间。
归并排序是原地排序吗?
不是。标准的归并排序要分配一个 O(n) 的临时缓冲区来合并两个半区,所以不是原地排序。原地合并的变体确实存在,但实现复杂,要么更慢,要么失去稳定性,所以通常还是用基于缓冲区的版本。
Coddy 编程语言插图

在 Coddy 掌握算法

开始