🎯 什麼情境該想到我

當你「需要「穩定」與「最差情況也保證 」這兩個保證」的時候。
★ 它比快速排序慢,卻是外部排序、平行排序、穩定排序這三個場景的唯一選擇。

⚙️ 怎麼用(機制與推導)

運作機制:分治三步

Divide  : 切成兩半(O(1),只是算中點)
Conquer : 遞迴排序兩半
Combine : ★ 合併兩個已排序的序列(O(n),整個演算法的重點在這)

合併是核心:兩個已排序序列,各放一根指標,每次取較小的那個。

左: [2 4 5]   右: [1 3 6]
     ↑              ↑        取 1
     ↑                ↑      取 2
       ↑              ↑      取 3
       ↑                ↑    取 4 → 5 → 6
結果: [1 2 3 4 5 6]
func MergeSort[T cmp.Ordered](a []T) {
    buf := make([]T, len(a))   // ★ 只配置一次,不要在遞迴裡每次 make
    mergeSort(a, buf)
}
 
func mergeSort[T cmp.Ordered](a, buf []T) {
    if len(a) < 2 {
        return
    }
    mid := len(a) / 2
    mergeSort(a[:mid], buf[:mid])
    mergeSort(a[mid:], buf[mid:])
    merge(a, mid, buf)
}
 
func merge[T cmp.Ordered](a []T, mid int, buf []T) {
    i, j, k := 0, mid, 0
    for i < mid && j < len(a) {
        // ★ 這裡必須是 <=,否則相等時會取右邊,穩定性就沒了
        if a[i] <= a[j] {
            buf[k] = a[i]; i++
        } else {
            buf[k] = a[j]; j++
        }
        k++
    }
    k += copy(buf[k:], a[i:mid])
    copy(buf[k:], a[j:])
    copy(a, buf)
}

★ 複雜度推導(主定理 Case 2)

對照主定理 :、 → 。
→ Case 2 → 。

不用主定理的直覺版:遞迴樹有 層,每一層的合併總成本都是 (每層處理的元素總數不變)→ 。

層 0:  ────────── n ──────────         合併成本 n
層 1:  ──── n/2 ────  ──── n/2 ────    合併成本 n
層 2:  ─ n/4 ─ ─ n/4 ─ ─ n/4 ─ ─ n/4 ─ 合併成本 n
       ⋮                                   ⋮       共 log n 層

這是最差、平均、最佳都一樣的 ——沒有壞情況。

四個性質

性質說明
穩定✅(靠 <=)
原地❌ 需要 輔助空間 —— 這是它最大的代價
最差保證✅ ,不像快排會退化
★ 只需要順序存取合併時兩邊都是從頭讀到尾

★ 那個「只需順序存取」是它真正的殺手鐧

三個場景因此只能用它:

場景為什麼
外部排序(資料大於記憶體)合併只需要順序讀取檔案/磁碟/網路串流。
經典做法:切成能塞進記憶體的段各自排好寫回磁碟(run),再做多路合併。
★ 快速排序需要隨機存取,在磁碟上是災難
鏈結串列排序合併不需要隨機存取 → 鏈結串列上可以做到 額外空間(只改指標,不搬資料)。這是唯一一個在鏈結串列上漂亮的 排序(見 鏈結串列)
平行排序左右兩半完全獨立,可以並行遞迴。合併階段也可平行化

★ TimSort:實務上你真正在用的東西

TimSort = 合併排序 + 插入排序 + 「偵測既有的有序片段」

  1. 掃描找出天然遞增/遞減的 run(真實資料常常有大段已排序)
  2. 太短的 run 用二分插入排序補到最小長度(見 插入排序)
  3. 依一套平衡規則合併這些 run
語言/執行環境排序實作穩定?
JS(V8)Array.prototype.sortTimSort(V8 7.0 起)✅ 保證穩定(ES2019 起是規格要求)
Python sorted / list.sortTimSort✅
Java Arrays.sort(Object[])TimSort✅
Go slices.Sortpdqsort(快排家族)❌ 不穩定。要穩定用 slices.SortStableFunc

★ 「為什麼 JS 的 sort 是穩定的、Go 的不是」——答案就是這張表。
Node.js 那側你可以依賴穩定性;Go 那側不可以,除非明確用 SortStableFunc。

🧪 我實際套用的紀錄

  • (待填)

⚠️ 注意 / 什麼時候不適用

  • ★ 記憶體是主要限制。 輔助空間,排 1 GB 資料就要多 1 GB。記憶體吃緊時用 堆積排序(原地且有保證)。
  • 不要在遞迴裡 make 緩衝區。每層都配置會產生大量 GC 壓力——配置一次傳下去(見上面程式碼)。
  • 實務上比快速排序慢。雖然同為 ,但快排原地、快取友善、常數小(見 快速排序)。
  • 原地合併排序存在但不值得。有 空間的合併演算法,但常數極大,實務上沒人用。
  • 小陣列上要切換成插入排序,否則遞迴開銷會吃掉優勢。
  • 不要自己寫。Go 用 slices.SortStableFunc,JS 直接用 Array.prototype.sort。

🔗 相關工具