快速排序 為原地、不穩定、平均 O(n log n) 的分治演算法。本文以「挖坑填數法」圖解分區過程,並提供標準遞迴實作與關鍵細節說明。
arr[low]。left、right 從兩端向中間掃描:
right 左移尋找 < pivot 的元素,填入 left 坑位。left 右移尋找 > pivot 的元素,填入 right 坑位。left == right,將 pivot 填入最終坑位。初始: [37] 40 38 42 461 5 7 9 12 (pivot=37, left=0, right=8)
right→ 找到 12<37: [12] 40 38 42 461 5 7 9 [12] left=1
left→ 找到 40>37: [12] [40] 38 42 461 5 7 9 40 right=7
right→ 找到 9<37: [12] 9 38 42 461 5 7 [9] 40 left=2
left→ 找到 38>37: [12] 9 [38] 42 461 5 7 38 40 right=6
right→ 找到 7<37: [12] 9 7 42 461 5 [7] 38 40 left=3
left→ 找到 42>37: [12] 9 7 [42] 461 5 42 38 40 right=5
right→ 找到 5<37: [12] 9 7 5 461 [5] 42 38 40 left=4
left→ 找到 461>37: [12] 9 7 5 [461] 461 42 38 40 right=4
left==right=4: [12] 9 7 5 [37] 461 42 38 40 ← 分區完成
public class QuickSort {
/** 公開入口 */
public static void sort(int[] arr) {
if (arr == null || arr.length <= 1) return;
quickSort(arr, 0, arr.length - 1);
}
private static void quickSort(int[] arr, int left, int right) {
if (left >= right) return;
int pivotIndex = partition(arr, left, right);
quickSort(arr, left, pivotIndex - 1);
quickSort(arr, pivotIndex + 1, right);
}
/** 單向掃描分區 (Lomuto 方案,較易理解) */
private static int partition(int[] arr, int left, int right) {
int pivot = arr[right]; // 選最右為基準
int i = left - 1; // 小於區域邊界
for (int j = left; j < right; j++) {
if (arr[j] <= pivot) {
i++;
swap(arr, i, j);
}
}
swap(arr, i + 1, right);
return i + 1;
}
/** 雙向掃描分區 (Hoare 方案,交換次數較少) */
private static int partitionHoare(int[] arr, int left, int right) {
int pivot = arr[left];
int i = left, j = right;
while (i < j) {
while (i < j && arr[j] >= pivot) j--;
if (i < j) arr[i++] = arr[j];
while (i < j && arr[i] <= pivot) i++;
if (i < j) arr[j--] = arr[i];
}
arr[i] = pivot;
return i;
}
private static void swap(int[] arr, int i, int j) {
int tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp;
}
// 測試
public static void main(String[] args) {
int[] a = {37, 40, 38, 42, 461, 5, 7, 9, 12};
sort(a);
System.out.println(Arrays.toString(a)); // [5, 7, 9, 12, 37, 38, 40, 42, 461]
}
}
| 策略 | 說明 | 效果 |
|---|---|---|
| 三數取中選基準 | 取 left, mid, right 中位數 | 避免已排序陣列退化 O(n²) |
| 小陣列改插入排序 | 長度 < 10~20 切換 | 減少遞迴開銷,提升常數因子 |
| 尾遞迴消除 | 遞迴處理較小分區,大分區迭代 | 棧深度 O(log n) → O(log n) 但常數更小 |
| 三向切分 | 重複鍵值多時分成 < = > 三段 | 含大量重複元素時 O(n) → O(n log n) |