場景
假設我們現在對“6 1 2 7 9 3 4 5 10 8”這個10個數進行升序排序
實現方法
1. 基準數法
- 首先在這個序列中隨便找一個數作爲基準數(不要被這個名詞嚇到了,就是一個用來參照的數,待會你就知道它用來做啥的了)爲了方便,就讓第一個數6作爲基準數吧。
- 接下來,需要將這個序列中所有比基準數大的數放在6的右邊,比基準數小的數放在6的左邊
- 在初始狀態下,數字6在序列的第1位。我們的目標是將6挪到序列中間的某個位置,假設這個位置是k。現在就需要尋找這個k,並且以第k位爲分界點,左邊的數都小於等於6,右邊的數都大於等於6。
- 用兩個變量i和j,分別指向序列最左邊和最右邊。即i=1,指向數字6。讓哨兵j指向序列的最右邊(即=10),指向數字8。
- 首先哨兵j開始出動。因爲此處設置的基準數是最左邊的數,所以需要讓哨兵j先出動,這一點非常重要(請自己想一想爲什麼)。哨兵j一步一步地向左挪動(即j–),直到找到一個小於6的數停下來。接下來哨兵i再一步一步向右挪動(即i++),直到找到一個數大於6的數停下來。最後哨兵j停在了數字5面前,哨兵i停在了數字7面前。
- 現在交換哨兵i和哨兵j所指向的元素的值(至少交換值,i、j位置不變)。交換之後的序列如下:
x x x x i x x x x j
6 1 2 5 9 3 4 7 10 8 (第一次交換結果)
以此類推:
x x x x x i x x j x x
6 1 2 5 4 3 9 7 10 8 (第二次交換結果)
x x x x x x ij x x
6 1 2 5 4 3 9 7 10 8 (第三次交換結果)
- 哨兵i和哨兵j都走到3面前。說明此時“探測”結束。我們將基準數6和3進行交換。交換之後的序列如下:
3 1 2 5 4 6 9 7 10 8
到此第一輪“探測”真正結束
回顧一下剛纔的過程,其實哨兵j的使命就是要找小於基準數的數,而哨兵i的使命就是要找大於基準數的數,直到i和j碰頭爲止。
- 此時我們已經將原來的序列,以6爲分界點拆分成了兩個序列,左邊的序列是“3 1 2 5 4”,右邊的序列是“9 7 10 8”。接下來還需要分別處理這兩個序列。因爲6左邊和右邊的序列目前都還是很混亂的。不過不要緊,我們已經掌握了方法,接下來只要模擬剛纔的方法分別處理6左邊和右邊的序列即可。現在先來處理6左邊的序列現吧。最終結果如下:
1 2 3 4 5 6 7 8 9 10
- 其實上述方法是利用了二分法的思想
- 代碼實現
package com.example.sort;
import java.util.Arrays;
public class QuickSortDemo01 {
public static void quickSort(Integer[] arr, int low, int high) {
int i, j, temp, t;
if (low > high) {
return;
}
i = low;
j = high;
//temp就是基準位
temp = arr[low];
while (i < j) {
//先從右邊,依次往左遞減
while (temp <= arr[j] && i < j) {
j--;
}
//再從左邊,依次往右遞增
while (temp >= arr[i] && i < j) {
i++;
}
//如果滿足條件則交換
if (i < j) {
t = arr[j];
arr[j] = arr[i];
arr[i] = t;
}
}
//最後將基準爲與i和j相等位置的數字交換
arr[low] = arr[i];
arr[i] = temp;
//遞歸調用左半數組
quickSort(arr, low, j - 1);
//遞歸調用右半數組
quickSort(arr, j + 1, high);
}
public static void main(String[] args) {
Integer[] arr = {16, 71, 2, 4, 7, 62, 13, 4, 2, 1, 8, 9, 19};
quickSort(arr, 0, arr.length - 1);
System.out.println(Arrays.asList(arr));
}
}
// [1, 2, 2, 4, 4, 7, 8, 9, 13, 16, 19, 62, 71]
2. 三數取中法
- 在上面基準數法中快排的過程中,每一次我們要取一個元素作爲樞紐值,以這個數字來將序列劃分爲兩部分。
- 在此我們採用三數取中法,也就是取左端、中間、右端三個數,然後進行(升序)排序,將中間數作爲樞紐值。
- 原始數據
4 5 7 8 1 2 3 6
此處中間數8是根據取模 左端[下標0]和右端[下標7]之和取模[ (0+7)/2 = 3 ]得到,8的下標爲3.
- 對4、8、6進行排序[ 4 6 8 ]並且把樞紐值6放在數組最後 [ 8之前 ]
4 5 7 1 2 3 6 8
- 根據樞紐值6進行分割
4 5 7 1 2 3 6 8
對分割之後的 5 7 1 2 3 採用基準數法進行排序
4 5 2 3 1 6 7 8 ( 第一輪分割完成)
- 遞歸在子序列進行上述相同處理(先三位取中,再以中值分割)
4 5 2 3 1 (左子序列)
7 8 ( 右子序列)
右子序列因爲已經是有序的,所以不用處理;對左子序列進行處理即可
- 最終排序結果爲:
1 2 3 4 5 6 7 8
- 代碼實現:
package com.example.sort;
import java.util.Arrays;
public class QuickSortDemo02 {
public static void main(String[] args) {
int[] arr = {9, 8, 7, 6, 5, 4, 3, 2, 1, 0};
quickSort(arr, 0, arr.length - 1);
System.out.println("排序結果:" + Arrays.toString(arr));
}
/**
* 排序
*/
public static void quickSort(int[] arr, int left, int right) {
if (left < right) {
//獲取樞紐值,並將其放在當前待處理序列末尾
dealPivot(arr, left, right);
//樞紐值被放在序列末尾
int pivot = right - 1;
//左指針
int i = left;
//右指針
int j = right - 1;
while (true) {
while (arr[++i] < arr[pivot]) {
}
while (j > left && arr[--j] > arr[pivot]) {
}
if (i < j) {
swap(arr, i, j);
} else {
break;
}
}
if (i < right) {
swap(arr, i, right - 1);
}
quickSort(arr, left, i - 1);
quickSort(arr, i + 1, right);
}
}
/**
* 處理樞紐值
*/
public static void dealPivot(int[] arr, int left, int right) {
int mid = (left + right) / 2;
if (arr[left] > arr[mid]) {
swap(arr, left, mid);
}
if (arr[left] > arr[right]) {
swap(arr, left, right);
}
if (arr[right] < arr[mid]) {
swap(arr, right, mid);
}
swap(arr, right - 1, mid);
}
/**
* 交換元素通用處理
*/
private static void swap(int[] arr, int a, int b) {
int temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
}
// 排序結果:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]