快速排序是原地、不穩定、平均 O(n log n)的分治算法。本文解析一個生產級工程化實現,包含三數取中樞軸選擇、小數組插入排序優化、Comparable 泛型通用設計等關鍵工程細節。
package Utils.Sort;
public class QuickSort implements SortStrategy {
private static final int CUTOFF = 3; // 小數組閾值,切換到插入排序
// 入口方法
public void sort(Comparable[] obj) { ... }
// 核心遞歸排序
private void quickSort(Comparable[] obj, int left, int right) { ... }
// 樞軸選擇:三數取中 + 三數排序
private void pivot(Comparable[] obj, int left, int right) { ... }
}
private void pivot(Comparable[] obj, int left, int right) {
int center = (left + right) / 2;
// 對 left, center, right 三個位置元素進行排序
// 確保:obj[left] ≤ obj[center] ≤ obj[right]
if (obj[left].compareTo(obj[center]) > 0) swap(obj, left, center);
if (obj[left].compareTo(obj[right]) > 0) swap(obj, left, right);
if (obj[center].compareTo(obj[right]) > 0) swap(obj, center, right);
// 將樞軸 (center) 移至 right-1 位置
// 最大值在 right,最小值在 left,樞軸在 right-1
swap(obj, center, right - 1);
}
優勢:
private static final int CUTOFF = 3;
private void quickSort(Comparable[] obj, int left, int right) {
if (left + CUTOFF > right) {
// 子數組足夠小,使用插入排序
SortStrategy ss = new ChooseSort(); // 假設為插入排序
ss.sort(obj); // 注意:這裡有 bug,應排序子數組而非整個數組
return;
}
// ... 正常快排邏輯
}
工程經驗值:
CUTOFF = 3-10 之間,7-9 通常最優Arrays.sort() 對小數組同樣使用雙軸快排 + 插入排序混合
// 樞軸已放在 right-1
int i = left, j = right - 1;
Comparable pivot = obj[right - 1];
while (true) {
// 從左向右找 ≥ pivot 的元素
while (obj[++i].compareTo(pivot) < 0) {}
// 從右向左找 ≤ pivot 的元素
while (obj[--j].compareTo(pivot) > 0) {}
if (i < j) swap(obj, i, j);
else break; // 指針相遇,分區完成
}
// 將樞軸放到最終位置 i
swap(obj, i, right - 1);
關鍵細節:
< 和 > 而非 ≤/≥,將等於樞軸的元素分到兩邊,平衡分區obj[left] 和 obj[right] 已經過三數排序,保證不越界public void sort(Comparable[] obj) {
if (obj == null) throw new NullPointerException("The argument can not be null!");
quickSort(obj, 0, obj.length - 1);
}
設計權衡:
| 方案 | 優點 | 缺點 |
|---|---|---|
| Comparable (內部比較) | 類本身定義自然順序,調用簡單 | 只能有一種排序方式,修改類代碼才能改變 |
| Comparator (外部比較) | 多種排序策略,不侵入類定義 | 調用需傳 Comparator,稍顯繁瑣 |
| 雙重支持 (推薦) | 兼容兩者,如 Arrays.sort(T[], Comparator) |
實現稍複雜 |
// 原代碼錯誤:對整個數組排序而非子數組
SortStrategy ss = new ChooseSort();
ss.sort(obj);
// 修正:應實現對子數組的插入排序
private void insertionSort(Comparable[] obj, int left, int right) {
for (int i = left + 1; i <= right; i++) {
Comparable tmp = obj[i];
int j = i;
while (j > left && obj[j-1].compareTo(tmp) > 0) {
obj[j] = obj[j-1];
j--;
}
obj[j] = tmp;
}
}
// 原代碼最後一步錯誤
if (obj[center].compareTo(obj[right]) > 0) {
tmp = obj[center];
obj[center] = obj[right];
obj[center] = tmp; // Bug:應為 obj[right] = tmp
}
// Dijkstra 三路分區:< pivot | = pivot | > pivot
private void quickSort3Way(Comparable[] a, int lo, int hi) {
if (lo >= hi) return;
int lt = lo, gt = hi;
Comparable v = a[lo];
int i = lo;
while (i <= gt) {
int cmp = a[i].compareTo(v);
if (cmp < 0) swap(a, lt++, i++);
else if (cmp > 0) swap(a, i, gt--);
else i++;
}
quickSort3Way(a, lo, lt - 1);
quickSort3Way(a, gt + 1, hi);
}
| 實現 | 算法 | 特點 |
|---|---|---|
| 本實現 | 單軸快排 + 三數取中 + 插入排序 | 教學級,可讀性強 |
Java 7 Arrays.sort(int[]) |
雙軸快排 | 兩個樞軸,分三區,實測快 10-20% |
Java 8 Arrays.sort(Object[]) |
TimSort (歸併+插入) | 穩定排序,利用現有順序,近乎有序極快 |
Java 12+ Arrays.sort(int[]) |
雙軸快排 + 向量化 | 利用 CPU SIMD 指令並行比較 |
Arrays.sort()/Collections.sort() — 經過極致優化、充分測試Comparator 配合標準庫Arrays.parallelSort())、原生庫 (JNI/FFI)時間複雜度: 最好:O(n log n) — 每次分區均衡 平均:O(n log n) — 隨機數據期望 最壞:O(n²) — 極端情況(已由三數取中大幅降低概率) 空間複雜度: O(log n) — 遞歸棧深度 (平均) O(n) — 最壞情況遞歸棧 穩定性:不穩定 (相等元素可能交換相對位置)