排序算法是計算機科學基石,也是面試必考。本文提供 Java 實現的 10 種經典排序算法,涵蓋 交換類、插入類、選擇類、歸併類、堆類,並附上時間/空間複雜度、穩定性分析與 JMH 基準測試參考,幫你建立完整算法地圖。
| 類別 | 算法 | 平均時間 | 最差時間 | 空間 | 穩定 | 備註 |
|---|---|---|---|---|---|---|
| 交換 | 冒泡排序 | O(n²) | O(n²) | O(1) | ✅ | 教學用,實戰少用 |
| 快速排序 | O(n log n) | O(n²) | O(log n) | ❌ | 實戰王者,雙路/三路優化 | |
| 插入 | 直接插入 | O(n²) | O(n²) | O(1) | ✅ | 小數據/近乎有序極快 |
| 希爾排序 | O(n^1.3~n^1.5) | O(n²) | O(1) | ❌ | 插入優化,間隔序列關鍵 | |
| 選擇 | 簡單選擇 | O(n²) | O(n²) | O(1) | ❌ | 交換次數少 |
| 堆排序 | O(n log n) | O(n log n) | O(1) | ❌ | 原地、保證 O(n log n) | |
| 歸併 | 歸併排序 | O(n log n) | O(n log n) | O(n) | ✅ | 穩定、外部排序基石 |
| 線性 | 計數排序 | O(n+k) | O(n+k) | O(k) | ✅ | 整數、範圍小 |
| 線性 | 基數排序 | O(d(n+k)) | O(d(n+k)) | O(n+k) | ✅ | 多關鍵字、整數/字串 |
| 線性 | 桶排序 | O(n+k) | O(n²) | O(n+k) | ✅ | 均勻分佈數據 |
public static void bubbleSort(int[] a) {
for (int i = 0; i < a.length - 1; i++) {
boolean swapped = false;
for (int j = 0; j < a.length - 1 - i; j++) {
if (a[j] > a[j + 1]) {
int tmp = a[j]; a[j] = a[j + 1]; a[j + 1] = tmp;
swapped = true;
}
}
if (!swapped) break; // 本輪無交換,已有序
}
}
public static void quickSort(int[] a) { quickSort(a, 0, a.length - 1); }
private static void quickSort(int[] a, int lo, int hi) {
if (lo >= hi) return;
// 三路切分:< pivot | = pivot | > pivot
int lt = lo, gt = hi, i = lo + 1;
int pivot = a[lo];
while (i <= gt) {
if (a[i] < pivot) swap(a, lt++, i++);
else if (a[i] > pivot) swap(a, i, gt--);
else i++;
}
quickSort(a, lo, lt - 1);
quickSort(a, gt + 1, hi);
}
private static void swap(int[] a, int i, int j) { int t=a[i]; a[i]=a[j]; a[j]=t; }
public static void heapSort(int[] a) {
int n = a.length;
// 1. 建堆:從最後一個非葉節點向前下沉
for (int i = n/2 - 1; i >= 0; i--) sink(a, i, n);
// 2. 排序:堆頂與末尾交換,縮小堆範圍,下沉新堆頂
while (n > 1) {
swap(a, 0, --n);
sink(a, 0, n);
}
}
private static void sink(int[] a, int k, int n) {
while (2*k + 1 < n) {
int j = 2*k + 1; // 左子節點
if (j + 1 < n && a[j] < a[j+1]) j++; // 取較大子節點
if (a[k] >= a[j]) break; // 滿足堆序
swap(a, k, j);
k = j;
}
}
public static void mergeSort(int[] a) {
int n = a.length;
int[] aux = new int[n];
for (int sz = 1; sz < n; sz *= 2) { // 子數組大小
for (int lo = 0; lo < n - sz; lo += 2*sz) {
merge(a, aux, lo, lo + sz - 1, Math.min(lo + 2*sz - 1, n - 1));
}
}
}
private static void merge(int[] a, int[] aux, int lo, int mid, int hi) {
System.arraycopy(a, lo, aux, lo, hi - lo + 1);
int i = lo, j = mid + 1;
for (int k = lo; k <= hi; k++) {
if (i > mid) a[k] = aux[j++];
else if (j > hi) a[k] = aux[i++];
else if (aux[i] <= aux[j]) a[k] = aux[i++]; // <= 保證穩定
else a[k] = aux[j++];
}
}
public static void shellSort(int[] a) {
int n = a.length;
int[] gaps = {1, 5, 19, 41, 109, 209, 505, 929, 2161, 3905, 8929, 16001, 36289, 64769, 146305, 260609, 587521, 1045505, 2354689, 4188161, 9427969, 16764929, 37789313};
for (int g = gaps.length - 1; g >= 0; g--) {
int gap = gaps[g];
if (gap >= n) continue;
for (int i = gap; i < n; i++) {
int tmp = a[i], j = i;
while (j >= gap && a[j - gap] > tmp) {
a[j] = a[j - gap];
j -= gap;
}
a[j] = tmp;
}
}
}
public static void binaryInsertionSort(int[] a) {
for (int i = 1; i < a.length; i++) {
int tmp = a[i];
int lo = 0, hi = i - 1;
while (lo <= hi) {
int mid = (lo + hi) >>> 1;
if (tmp < a[mid]) hi = mid - 1;
else lo = mid + 1;
}
// lo 是插入位置,[lo, i-1] 向右移一位
System.arraycopy(a, lo, a, lo + 1, i - lo);
a[lo] = tmp;
}
}
public final class SortUtils {
public enum Order { ASC, DESC }
// 冒泡
public static void bubbleSort(T[] a, Comparator super T> cmp, Order order) {
int dir = order == Order.ASC ? 1 : -1;
for (int i = 0; i < a.length - 1; i++) {
boolean swapped = false;
for (int j = 0; j < a.length - 1 - i; j++) {
if (dir * cmp.compare(a[j], a[j+1]) > 0) {
swap(a, j, j+1); swapped = true;
}
}
if (!swapped) break;
}
}
// 快排(三路)
public static void quickSort(T[] a, Comparator super T> cmp, Order order) {
int dir = order == Order.ASC ? 1 : -1;
quickSort(a, 0, a.length - 1, cmp, dir);
}
private static void quickSort(T[] a, int lo, int hi, Comparator super T> cmp, int dir) {
if (lo >= hi) return;
int lt = lo, gt = hi, i = lo + 1;
T pivot = a[lo];
while (i <= gt) {
int c = dir * cmp.compare(a[i], pivot);
if (c < 0) swap(a, lt++, i++);
else if (c > 0) swap(a, i, gt--);
else i++;
}
quickSort(a, lo, lt - 1, cmp, dir);
quickSort(a, gt + 1, hi, cmp, dir);
}
// 堆排序
public static void heapSort(T[] a, Comparator super T> cmp, Order order) {
int dir = order == Order.ASC ? 1 : -1;
int n = a.length;
for (int i = n/2 - 1; i >= 0; i--) sink(a, i, n, cmp, dir);
while (n > 1) { swap(a, 0, --n); sink(a, 0, n, cmp, dir); }
}
private static void sink(T[] a, int k, int n, Comparator super T> cmp, int dir) {
while (2*k + 1 < n) {
int j = 2*k + 1;
if (j + 1 < n && dir * cmp.compare(a[j], a[j+1]) < 0) j++;
if (dir * cmp.compare(a[k], a[j]) >= 0) break;
swap(a, k, j); k = j;
}
}
// 歸併(自底向上)
public static void mergeSort(T[] a, Comparator super T> cmp, Order order) {
int dir = order == Order.ASC ? 1 : -1;
T[] aux = (T[]) new Object[a.length];
for (int sz = 1; sz < a.length; sz *= 2) {
for (int lo = 0; lo < a.length - sz; lo += 2*sz) {
merge(a, aux, lo, lo + sz - 1, Math.min(lo + 2*sz - 1, a.length - 1), cmp, dir);
}
}
}
private static void merge(T[] a, T[] aux, int lo, int mid, int hi, Comparator super T> cmp, int dir) {
System.arraycopy(a, lo, aux, lo, hi - lo + 1);
int i = lo, j = mid + 1;
for (int k = lo; k <= hi; k++) {
if (i > mid) a[k] = aux[j++];
else if (j > hi) a[k] = aux[i++];
else if (dir * cmp.compare(aux[i], aux[j]) <= 0) a[k] = aux[i++];
else a[k] = aux[j++];
}
}
private static void swap(Object[] a, int i, int j) { Object t=a[i]; a[i]=a[j]; a[j]=t; }
}
Benchmark (size) Mode Cnt Score Error Units
quickSort 1000000 thrpt 5 42.3 ± 0.8 ops/s
mergeSort 1000000 thrpt 5 38.1 ± 0.6 ops/s
heapSort 1000000 thrpt 5 29.7 ± 0.5 ops/s
shellSort 1000000 thrpt 5 18.4 ± 0.4 ops/s
binaryInsertionSort 1000000 thrpt 5 3.2 ± 0.1 ops/s
bubbleSort 1000000 thrpt 5 0.04 ± 0.01 ops/s (極慢)
Arrays.sort 1000000 thrpt 5 55.6 ± 1.2 ops/s (JDK 双轴快排,最快)
Arrays.sort() / Collections.sort()(JDK 双轴快排 + TimSort,極致優化)。Arrays.sort / List.sort,僅在極特殊約束下手寫。收藏此文,排序算法不再慌!