以 8 位運動員身高排序為例:[181, 169, 187, 172, 163, 191, 189, 182] (A-H)。
基準 pivot = 181 (A) 初始: i=0(L), j=7(R), pivot=181, empty=0 步驟 1: j 從右向左找 < 181 j=7: 182 ≥ 181 → j=6 j=6: 189 ≥ 181 → j=5 j=5: 191 ≥ 181 → j=4 j=4: 163 < 181 找到!a[empty]=a[4] → a[0]=163, empty=4 陣列: [163, 169, 187, 172, 163, 191, 189, 182] (empty=4) 步驟 2: i 從左向右找 > 181 i=1: 169 ≤ 181 → i=2 i=2: 187 > 181 找到!a[empty]=a[2] → a[4]=187, empty=2 陣列: [163, 169, 187, 172, 187, 191, 189, 182] (empty=2) 步驟 3: j 繼續找 < 181 j=3: 172 < 181 找到!a[empty]=a[3] → a[2]=172, empty=3 陣列: [163, 169, 172, 172, 187, 191, 189, 182] (empty=3) 步驟 4: i 繼續找 > 181 i=3: i==j 相遇,停止 步驟 5: 基準歸位 a[empty]=pivot → a[3]=181 陣列: [163, 169, 172, 181, 187, 191, 189, 182] 分區完成: 左側 [163,169,172] 都 < 181,右側 [187,191,189,182] 都 > 181
左半部 [163,169,172]: 已有序 (或繼續遞歸) 右半部 [187,191,189,182]: pivot=187 → 分區後 [182] [187] [191,189] [191,189] → pivot=191 → [189] [191] 最終有序: [163, 169, 172, 181, 182, 187, 189, 191]
public class QuickSortVisual {
public static void quickSort(int[] a) { quickSort(a, 0, a.length - 1); }
private static void quickSort(int[] a, int lo, int hi) {
if (lo >= hi) return;
int i = lo, j = hi;
int pivot = a[lo];
int empty = lo; // 空位索引
while (i < j) {
// 右指針找小於 pivot
while (i < j && pivot <= a[j]) j--;
if (i < j) a[empty] = a[empty = j]; // 填空位、更新空位
// 左指針找大於 pivot
while (i < j && a[i] <= pivot) i++;
if (i < j) a[empty] = a[empty = i]; // 填空位、更新空位
}
a[empty] = pivot; // 基準歸位
// 遞歸排序左右
quickSort(a, lo, empty - 1);
quickSort(a, empty + 1, hi);
}
public static void main(String[] args) {
int[] heights = {181, 169, 187, 172, 163, 191, 189, 182};
System.out.println("排序前: " + Arrays.toString(heights));
quickSort(heights);
System.out.println("排序後: " + Arrays.toString(heights));
}
}
// 選最後元素為 pivot,單指針 i 維護小於區域邊界
private static int partitionLomuto(int[] a, int lo, int hi) {
int pivot = a[hi];
int i = lo - 1;
for (int j = lo; j < hi; j++) {
if (a[j] <= pivot) swap(a, ++i, j);
}
swap(a, i + 1, hi);
return i + 1;
}
特點:代碼更簡潔、易理解,但交換次數較多、不穩定、退化性能略差。
| 階段 | 關鍵動作 | 不變量 |
|---|---|---|
| 初始化 | 選 pivot、空位=lo、i=lo、j=hi | 空位持有 pivot 值 |
| 右掃描 | j-- 找 < pivot,填入空位 | [lo, empty) < pivot |
| 左掃描 | i++ 找 > pivot,填入空位 | (empty, hi] > pivot |
| 相遇 | i==j,將 pivot 填入空位 | 基準歸位,左右分區完成 |
| 錯誤 | 後果 | 修正 |
|---|---|---|
循環條件 i <= j | 指針越界、重複處理 | i < j |
| 比較方向反 | 分區邏輯錯誤、無限循環 | 右找 < 、左找 > (升序) |
| 忘記基準歸位 | pivot 丟失、數據不全 | a[empty] = pivot |
| 遞歸邊界錯 | 棧溢出、漏排 | lo >= hi 返回 |