隨機化快速排序

import random

def randomized_quicksort(arr):
    if len(arr) <= 1:
        return arr
    pivot = random.choice(arr)
    left = [x for x in arr if x < pivot]
    mid = [x for x in arr if x == pivot]
    right = [x for x in arr if x > pivot]
    return randomized_quicksort(left) + mid + randomized_quicksort(right)

def randomized_quickselect(arr, k):
    """找到第 k 小的元素 (0-indexed)"""
    if len(arr) == 1:
        return arr[0]
    pivot = random.choice(arr)
    left = [x for x in arr if x < pivot]
    mid = [x for x in arr if x == pivot]
    right = [x for x in arr if x > pivot]
    if k < len(left):
        return randomized_quickselect(left, k)
    elif k < len(left) + len(mid):
        return pivot
    else:
        return randomized_quickselect(right, k - len(left) - len(mid))

# 比較:固定 pivot vs 隨機 pivot
import time
for n in [1000, 10000]:
    arr = list(range(n))
    random.shuffle(arr)
    start = time.time()
    randomized_quicksort(arr[:])
    t1 = time.time() - start
    print(f"n={n}: 隨機 pivot = {t1:.4f}s")

深入分析:為何隨機 pivot 有效?

固定 pivot 的最壞情況

固定 pivot(如選第一個元素)在已排序反排序輸入上會產生:

  • 每次分割:1 個 pivot + n-1 個元素在一邊
  • 遞迴深度:O(n) → 總時間 O(n²)

隨機 pivot 的期望分析

隨機 pivot 的期望時間為 O(n log n),原因:

  1. 隨機選 pivot = 均勻隨機分割
  2. 期望上 pivot 至少有 25% 機率落在中間 50%
  3. 好的分割讓問題規模指數級下降
  4. 期望深度 O(log n),每層 O(n) → 總和 O(n log n)

Quickselect 的期望時間

# 驗證線性期望
def expected_time_quickselect(n, trials=100):
    total_time = 0
    for _ in range(trials):
        arr = list(range(n))
        random.shuffle(arr)
        k = random.randint(0, n-1)
        start = time.time()
        randomized_quickselect(arr, k)
        total_time += time.time() - start
    return total_time / trials

for n in [100, 1000, 10000, 100000]:
    t = expected_time_quickselect(n, trials=50)
    print(f"n={n:6d}: 期望時間={t:.6f}s, T/n={t/n:.8f}")
# T/n 趨近常數 → 證明期望 O(n)

關鍵要點

  • ✅ Randomized Quicksort 期望時間 O(n log n),無最壞情況
  • ✅ Randomized Quickselect 期望時間 O(n)
  • ✅ 隨機化 = 消除對手攻擊(adversarial input)
  • ✅ 隨機 pivot 的期望分析依賴「線性期望」性質
  • ✅ Las Vegas 演算法允許無限壞的運氣(機率極低)

常見錯誤

程式碼範例

隨機化快速排序為什麼這麼重要?

你可能覺得快速排序已經是老掉牙的題目了,但加入「隨機化」這個關鍵元素後,它展現了幾個非常重要的概念:

為什麼需要隨機化?

傳統的 QuickSort 選擇固定 pivot(例如第一個元素)會導致 Worst Case O(n²)——當輸入已經是排序好的陣列時。這不是理論上的邊緣案例,而是真實世界中 API 回傳的資料常常就是已經排序好的!

隨機選 pivot 將 Worst Case 的發生機率降到幾乎為零。任何輸入陣列,隨機化 QuickSort 的期望時間都是 O(n log n)

平均情況 vs 最壞情況

| 特性 | 傳統 QuickSort | 隨機化 QuickSort | |:----|:-------------|:----------------| | Worst Case | O(n²) — 已排序陣列 | O(n²) — 機率趨近於 0 | | Average Case | O(n log n) | O(n log n) | | 實作難度 | 簡單 | 簡單(多一行 random) | | 實戰選擇 | ❌ 不安全 | ✅ 業界標準 |

應用在真實問題

  • 資料庫排序:PostgreSQL 的排序演算法使用隨機化 QuickSort
  • 即時資料處理:串流資料的 Top-K 選擇
  • 推薦系統:對分數進行排名選取

下一章預告:Monte Carlo 方法

QuickSort 是 Las Vegas 類型——保證正確但時間隨機。下一章的 Monte Carlo 方法是另一種極端——時間固定但答案可能錯誤。你會學到如何在科學計算和機器學習中利用這種「近似」策略。

解鎖完整教學內容

本章為付費內容。加入專案即可解鎖超過 5000 字的深度解析,包含 10 個以上神級 Prompt 與真實 Source Code 範例!