合併排序是經典的「分治法」代表,以穩定的 O(n log n) 時間複雜度著稱,且為穩定排序。本文將從核心思想、遞歸實作、自底向上迭代實作、複雜度分析到實戰優化,完整呈現這項必會演算法。
合併關鍵:雙指針線性掃描,時間 O(n)、額外空間 O(n)。
public class MergeSortRecursive {
public void sort(int[] a) {
int[] aux = new int[a.length];
mergeSort(a, 0, a.length - 1, aux);
}
private void mergeSort(int[] a, int lo, int hi, int[] aux) {
if (hi <= lo) return;
int mid = lo + (hi - lo) / 2;
mergeSort(a, lo, mid, aux);
mergeSort(a, mid + 1, hi, aux);
merge(a, lo, mid, hi, aux);
}
private void merge(int[] a, int lo, int mid, int hi, int[] aux) {
// 複製到輔助陣列
for (int k = lo; k <= hi; k++) aux[k] = a[k];
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++];
}
}
}
關鍵點:使用單一輔助陣列 aux 避免重複分配記憶體;<= 保證穩定性。
public class MergeSortIterative {
public void sort(int[] a) {
int N = a.length;
int[] aux = new int[N];
for (int sz = 1; sz < N; sz += sz) { // 子陣列大小:1, 2, 4, 8...
for (int lo = 0; lo < N - sz; lo += sz + sz) {
int mid = lo + sz - 1;
int hi = Math.min(lo + sz + sz - 1, N - 1);
merge(a, lo, mid, hi, aux);
}
}
}
// merge 方法同上
}
優勢:無遞歸呼叫棧開銷,適合嵌入式環境;邏輯較簡潔。
| 指標 | 數值 | 備註 |
|---|---|---|
| 最佳時間 | O(n log n) | 無論輸入分佈 |
| 最差時間 | O(n log n) | 同上 |
| 平均時間 | O(n log n) | 同上 |
| 空間複雜度 | O(n) | 輔助陣列 |
| 穩定性 | 穩定 | 相等元素相對順序不變 |
hi - lo <= 7 時改用插入排序,減少遞歸開銷。a[mid] <= a[mid+1] 則跳過合併。a、aux 交換引用),省去複製步驟。合併排序以穩定、確定的 O(n log n) 表現,成為教學與工程的標桿演算法。掌握遞歸與迭代雙重實作,並理解優化細節,能在面試與實戰中遊刃有餘。