场景
假设我们现在对“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]