快速排序是原地、分治、不穩定、平均 O(n log n) 的排序王者。本文從核心分區邏輯、基準選擇策略、遞歸尾調用優化、隨機化防退化、三向切分處理重複鍵、工程級測試用例設計**六個維度,提供可直接用於生產/面試的 C 語言完整實現。
| 特性 | Hoare 分區 (原版) | Lomuto 分區 (教科書版) |
|---|---|---|
| 基準位置 | 通常首元素 | 通常尾元素 |
| 指針移動 | 雙向向中間掃描 | 單向掃描 + 邊界指針 |
| 交換次數 | 較少 (約 1/3) | 較多 |
| 代碼複雜度 | 稍高 (需處理指針交叉) | 低 (直觀易懂) |
| 全等數組性能 | O(n log n) (指針交叉即停) | O(n²) (退化) |
推薦:工程採用 Hoare 分區 + 隨機化基準 + 三向切分 綜合方案。
#include
#include
// 隨機交換基準到首位,防止有序/逆序數組退化
static void swap(int *a, int *b) { int t = *a; *a = *b; *b = t; }
static int partition_hoare(int arr[], int low, int high) {
// 隨機選取基準並交換到 low
swap(&arr[low], &arr[low + rand() % (high - low + 1)]);
int pivot = arr[low];
int i = low - 1, j = high + 1;
while (1) {
do { i++; } while (arr[i] < pivot);
do { j--; } while (arr[j] > pivot);
if (i >= j) return j; // 分界點 j
swap(&arr[i], &arr[j]);
}
}
void quick_sort(int arr[], int n) {
if (!arr || n <= 1) return;
srand((unsigned)time(NULL)); // 僅主調用處種子一次
_quick_sort(arr, 0, n - 1);
}
static void _quick_sort(int arr[], int low, int high) {
if (low >= high) return;
int p = partition_hoare(arr, low, high);
_quick_sort(arr, low, p);
_quick_sort(arr, p + 1, high);
}
遞歸深度最壞 O(n),平均 O(log n)。總是遞歸處理較小分區,大分區用循環迭代,保證棧深度 ≤ O(log n)。
static void _quick_sort_opt(int arr[], int low, int high) {
while (low < high) {
// 小數組閾值切換插入排序 (通常 10~20)
if (high - low < 16) { insertion_sort(arr, low, high); return; }
int p = partition_hoare(arr, low, high);
// 遞歸處理較小側,迭代處理較大側
if (p - low < high - p) {
_quick_sort_opt(arr, low, p);
low = p + 1; // 尾遞歸 → 循環
} else {
_quick_sort_opt(arr, p + 1, high);
high = p;
}
}
}
將數組分為 < pivot、= pivot、> pivot 三段,等於區不再遞歸。荷蘭國旗算法 (Dijkstra)。
static void partition_3way(int arr[], int low, int high, int *lt, int *gt) {
swap(&arr[low], &arr[low + rand() % (high - low + 1)]);
int pivot = arr[low];
int i = low, lt_idx = low, gt_idx = high;
while (i <= gt_idx) {
if (arr[i] < pivot) swap(&arr[lt_idx++], &arr[i++]);
else if (arr[i] > pivot) swap(&arr[i], &arr[gt_idx--]);
else i++;
}
*lt = lt_idx; *gt = gt_idx; // 返回等於區邊界 [lt, gt]
}
元素 ≤ 16 時插入排序比快排更快 (無函數調用開銷、緩存局部性好)。
void quick_sort_production(int arr[], int n) {
if (!arr || n <= 1) return;
srand((unsigned)time(NULL));
_quick_sort_3way(arr, 0, n - 1);
}
static void _quick_sort_3way(int arr[], int low, int high) {
while (high - low >= 16) {
int lt, gt;
partition_3way(arr, low, high, <, >);
// 遞歸較小側
if (lt - low < high - gt) {
_quick_sort_3way(arr, low, lt - 1);
low = gt + 1;
} else {
_quick_sort_3way(arr, gt + 1, high);
high = lt - 1;
}
}
insertion_sort(arr, low, high); // 收尾
}
| 測試類別 | 用例描述 | 驗證點 |
|---|---|---|
| 邊界 | NULL 指針、長度 0/1 | 參數檢查、早退 |
| 基礎有序性 | 已升序、已降序、全等元素 | 隨機基準防退化、三向切分 |
| 隨機分佈 | 均勻隨機、正態分佈、少量唯一值 | 平均性能、重複鍵處理 |
| 極值 | INT_MIN/MAX 混合、大量重複極值 | 溢出安全 (無加法)、比較正確 |
| 穩定性 | 結構體含 key+id,驗證相同 key 相對序 | 確認不穩定性 (預期) |
| 性能基準 | 10⁴/10⁵/10⁶ 隨機整數,對比 std::sort | 常數因子、緩存友好度 |
swap(arr[i], arr[j]) 跨越中間相等元素)。快速排序的工程化核心在於「隨機化消除最壞案、三向切分征服重複鍵、尾遞歸限制棧深、小數組插入排序減常數」。掌握 Hoare 分區雙指針邏輯與三向切分荷蘭國旗算法,配合完整測試矩陣,即可在面試白板編程與系統級組件開發中游刃有餘。標準庫 std::sort (Introsort) 正是此思想的集大成者。