归并排序
最后更新
归并排序是一种分治算法。它递归地把数组一分为二,直到每一段只剩一个元素(单个元素天然有序),然后按顺序把这些片段合并回去。合并时用两个指针遍历两个有序子数组,每次把较小的那个队首元素复制到结果中。点击上方的“播放”,观看数组如何经过一次次合并重新拼好。
因为每次都对半拆分,归并排序在任何情况下都是 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))Merge Sort 的 JavaScript 代码
JavaScript
1function mergeSort(arr) {2 if (arr.length <= 1) return arr;3 const mid = Math.floor(arr.length / 2);4 const left = mergeSort(arr.slice(0, mid));5 const right = mergeSort(arr.slice(mid));6 return merge(left, right);7}8
9// Merge two sorted arrays into one sorted array10function merge(left, right) {11 const out = [];12 let i = 0;13 let j = 0;14 while (i < left.length && j < right.length) {15 out.push(left[i] <= right[j] ? left[i++] : right[j++]);16 }17 return out.concat(left.slice(i), right.slice(j));18}19
20const data = [5, 2, 9, 1, 7, 3];21console.log("Before:", data);22console.log("Sorted:", mergeSort(data));Merge Sort 的 Java 代码
Java
1import java.util.Arrays;2
3public class Main {4 static void mergeSort(int[] arr, int left, int right) {5 if (left >= right) return;6 int mid = (left + right) / 2;7 mergeSort(arr, left, mid);8 mergeSort(arr, mid + 1, right);9 merge(arr, left, mid, right);10 }11
12 // Merge two sorted halves into a temp array, then copy back13 static void merge(int[] arr, int left, int mid, int right) {14 int[] tmp = new int[right - left + 1];15 int i = left, j = mid + 1, k = 0;16 while (i <= mid && j <= right) {17 tmp[k++] = arr[i] <= arr[j] ? arr[i++] : arr[j++];18 }19 while (i <= mid) tmp[k++] = arr[i++];20 while (j <= right) tmp[k++] = arr[j++];21 System.arraycopy(tmp, 0, arr, left, tmp.length);22 }23
24 public static void main(String[] args) {25 int[] arr = {38, 27, 43, 3, 9, 82, 10};26 System.out.println("Before: " + Arrays.toString(arr));27 mergeSort(arr, 0, arr.length - 1);28 System.out.println("After: " + Arrays.toString(arr));29 }30}Merge Sort 的 C++ 代码
C++
1#include <iostream>2#include <vector>3
4void printVec(const std::vector<int>& a) {5 for (int x : a) std::cout << x << " ";6 std::cout << "\n";7}8
9void merge(std::vector<int>& a, int lo, int mid, int hi) {10 std::vector<int> tmp;11 tmp.reserve(hi - lo + 1);12 int i = lo, j = mid + 1;13 while (i <= mid && j <= hi) {14 if (a[i] <= a[j]) tmp.push_back(a[i++]);15 else tmp.push_back(a[j++]);16 }17 while (i <= mid) tmp.push_back(a[i++]);18 while (j <= hi) tmp.push_back(a[j++]);19 for (size_t k = 0; k < tmp.size(); ++k) a[lo + k] = tmp[k];20}21
22void mergeSort(std::vector<int>& a, int lo, int hi) {23 if (lo >= hi) return;24 int mid = lo + (hi - lo) / 2;25 mergeSort(a, lo, mid); // sort the left half26 mergeSort(a, mid + 1, hi); // sort the right half27 merge(a, lo, mid, hi); // merge the sorted halves28}29
30int main() {31 std::vector<int> data = {38, 27, 43, 3, 9, 82, 10};32 std::cout << "Before: ";33 printVec(data);34 mergeSort(data, 0, static_cast<int>(data.size()) - 1);35 std::cout << "After: ";36 printVec(data);37 return 0;38}Merge Sort 的 C 代码
C
1#include <stdio.h>2#include <stdlib.h>3
4void printArr(const int a[], int n) {5 for (int i = 0; i < n; i++) printf("%d ", a[i]);6 printf("\n");7}8
9void merge(int a[], int lo, int mid, int hi) {10 int* tmp = malloc((hi - lo + 1) * sizeof(int));11 int i = lo, j = mid + 1, k = 0;12 while (i <= mid && j <= hi) {13 if (a[i] <= a[j]) tmp[k++] = a[i++];14 else tmp[k++] = a[j++];15 }16 while (i <= mid) tmp[k++] = a[i++];17 while (j <= hi) tmp[k++] = a[j++];18 for (k = 0; k <= hi - lo; k++) a[lo + k] = tmp[k];19 free(tmp);20}21
22void mergeSort(int a[], int lo, int hi) {23 if (lo >= hi) return;24 int mid = lo + (hi - lo) / 2;25 mergeSort(a, lo, mid); // sort the left half26 mergeSort(a, mid + 1, hi); // sort the right half27 merge(a, lo, mid, hi); // merge the sorted halves28}29
30int main(void) {31 int data[] = {38, 27, 43, 3, 9, 82, 10};32 int n = sizeof(data) / sizeof(data[0]);33 printf("Before: ");34 printArr(data, n);35 mergeSort(data, 0, n - 1);36 printf("After: ");37 printArr(data, n);38 return 0;39}Merge Sort 的 Pseudocode 代码
Pseudocode
1DECLARE nums : ARRAY[1:7] OF INTEGER2DECLARE n : INTEGER3n ← 74nums[1] ← 75nums[2] ← 36nums[3] ← 97nums[4] ← 18nums[5] ← 59nums[6] ← 810nums[7] ← 211DECLARE temp : ARRAY[1:7] OF INTEGER12DECLARE i : INTEGER13
14PROCEDURE merge(lo : INTEGER, mid : INTEGER, hi : INTEGER)15 DECLARE a : INTEGER16 DECLARE b : INTEGER17 DECLARE k : INTEGER18 a ← lo19 b ← mid + 120 k ← lo21 WHILE a <= mid AND b <= hi DO22 IF nums[a] <= nums[b] THEN23 temp[k] ← nums[a]24 a ← a + 125 ELSE26 temp[k] ← nums[b]27 b ← b + 128 ENDIF29 k ← k + 130 ENDWHILE31 WHILE a <= mid DO32 temp[k] ← nums[a]33 a ← a + 134 k ← k + 135 ENDWHILE36 WHILE b <= hi DO37 temp[k] ← nums[b]38 b ← b + 139 k ← k + 140 ENDWHILE41 FOR k ← lo TO hi42 nums[k] ← temp[k]43 NEXT k44ENDPROCEDURE45
46PROCEDURE mergeSort(lo : INTEGER, hi : INTEGER)47 DECLARE mid : INTEGER48 IF lo < hi THEN49 mid ← (lo + hi) DIV 250 // Sort each half, then merge them51 CALL mergeSort(lo, mid)52 CALL mergeSort(mid + 1, hi)53 CALL merge(lo, mid, hi)54 ENDIF55ENDPROCEDURE56
57CALL mergeSort(1, n)58
59FOR i ← 1 TO n60 OUTPUT nums[i]61NEXT i归并排序常见问题
归并排序的时间复杂度是多少?
归并排序在最好、平均和最坏情况下都是
O(n log n),因为它总是把数组对半拆分。合并缓冲区需要 O(n) 的额外空间。归并排序是稳定的吗?
是的,只要合并时遇到相等元素优先从左半部分取。这样相等元素就能保持原来的相对顺序,这也是归并排序常被用作稳定排序的原因。
为什么选归并排序而不是快速排序?
归并排序和快速排序有什么区别?
两者都是分治排序,但快速排序围绕基准值分区,原地排序,只需
O(log n) 的栈空间;归并排序不看数据直接对半拆分,再借助 O(n) 的缓冲区合并。由于缓存局部性更好,快速排序实际上通常更快,但归并排序有保证的 O(n log n) 最坏情况,而且是稳定的。实际开发中什么时候该用归并排序?
当你需要有
O(n log n) 保证的稳定排序时,对链表排序时(它不需要随机访问),或者对大到内存放不下的数据做外部排序时,就用归并排序。内存紧张时要避免使用,因为它需要 O(n) 的额外空间。归并排序是原地排序吗?
不是。标准的归并排序要分配一个
O(n) 的临时缓冲区来合并两个半区,所以不是原地排序。原地合并的变体确实存在,但实现复杂,要么更慢,要么失去稳定性,所以通常还是用基于缓冲区的版本。