数据结构基础之《(10)—快速排序》
一、快速排序基础
1、Partition过程
给定一个数组arr,和一个整数num。请把小于等于num的数放在数组的左边,大于num的数放在数组的右边。
要求额外空间复杂度O(1),时间复杂度O(N)
2、例子
区分小于等于num的数
(<=区) [5 3 7 2 3 4 1]
num = 3
设计一个区域,叫小于等于区,放在数组前面
遍历数组:
(1)i位置的数<=num
把当前数和小于等于区的下一个数交换,然后小于等于区向右扩一个位置,然后i++
(2)i位置的数>num
直接i++
第一个数5,i直接跳下一个
第二个数3,把3和5交换,小于等于区右移一个位置,i++
(<=区 3) [5 7 2 3 4 1]
第三个数7,i直接跳下一个
第四个数2,把2和5交换,小于等于区右移一个位置,i++
(<=区 3 2) [7 5 3 4 1]
第五个数3,把3和7交换,小于等于区右移一个位置,i++
(<=区 3 2 3) [5 7 4 1]
第六个数4,i直接跳下一个
第七个数1,把1和5交换,小于等于区右移一个位置,i++
(<=区 3 2 3 1) [7 4 5]
3、荷兰国旗问题,给你一个数组,和给定num,将数组分成三块
(<num区) | (=num区) | (>num区)
[3 5 4 0 4 6 7 2]
num = 4
设计两个区域,小于区域和大于区域
(<区) [3 5 4 0 4 6 7 2] (>区)
遍历数组:
(1)i位置的数==num,i++
(2)i位置的数<num,把i位置的数与<区右一个交换,<区右扩,i++
(3)i位置的数>num,把i位置的数与>区左一个交换,>区左扩,i不动
第一个数3,小于num,自己和自己交换,小于区右移一个位置,i++
(<区 3) [5 4 0 4 6 7 2] (>区)
第二个数5,大于num,5和2交换,大于区左移一个位置,i不动
(<区 3) [2 4 0 4 6 7] (5 >区)
i留在原地,第三个数是2,小于num,自己和自己交换,小于区右移一个位置,i++
(<区 3 2) [4 0 4 6 7] (5 >区)
第三个数4,等于num,i++
(<区 3 2) [4 0 4 6 7] (5 >区)
第四个数0,大于num,0和4交换,小于区右移一个位置,i++
(<区 3 2 0) [4 4 6 7] (5 >区)
第五个数4,等于num,i++
(<区 3 2 0) [4 4 6 7] (5 >区)
第六个数6,大于num,6和7交换,大于区左移一个位置,i不动
(<区 3 2 0) [4 4 7] (6 5 >区)
第七个数7,大于num,自己和自己交换,大于区左移一个位置,i不动
(<区 3 2 0) [4 4] (7 6 5 >区)
二、代码
package class03;
/**
* 快速排序
*/
public class Code03_PartitionAndQuickSort {
public static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
public static int partition(int[] arr, int L, int R) {
if (L > R) {
return -1;
}
if (L == R) {
return L;
}
int lessEqual = L - 1;
int index = L;
while (index < R) {
if (arr[index] <= arr[R]) {
swap(arr, index, ++lessEqual);
}
index++;
}
swap(arr, ++lessEqual, R);
return lessEqual;
}
/**
* 在arr[L...R]范围上玩荷兰国旗问题划分,以arr[R]做划分值
* 在[L...R]范围内,<arr[R]放左侧,==arr[R]放中间,>arr[R]放在右边
* @param arr
* @param L
* @param R
* @return
*/
public static int[] netherlandsFlag(int[] arr, int L, int R) {
if (L > R) { //如果范围不是有效范围
return new int[] {-1, -1};
}
if (L == R) { //说明L到R只有一个数
return new int[] {L, R};
}
int less = L - 1; //<区右边界
int more = R; //>区左边界
int index = L;
while (index < more) {
if (arr[index] == arr[R]) {
index++;
} else if (arr[index] < arr[R]) {
swap(arr, index++, ++less);
} else {
swap(arr, index, --more);
}
}
swap(arr, more, R);
return new int[] {less + 1, more};
}
/**
* 快排1.0版本
* @param arr
*/
public static void quickSort1(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
process1(arr, 0, arr.length - 1);
}
public static void process1(int[] arr, int L, int R) {
if (L >= R) {
return;
}
int M = partition(arr, L, R);
process1(arr, L, M - 1); //左侧范围重复玩递归
process1(arr, M + 1, R); //右侧范围重复玩递归
}
/**
* 快排2.0版本
* @param arr
*/
public static void quickSort2(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
process2(arr, 0, arr.length - 1);
}
public static void process2(int[] arr, int L, int R) {
if (L >= R) {
return;
}
int[] equalArea = netherlandsFlag(arr, L, R);
process2(arr, L, equalArea[0] - 1);
process2(arr, equalArea[1] + 1, R);
}
/**
* 快排3.0版本
* @param arr
*/
public static void quickSort3(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
process3(arr, 0, arr.length - 1);
}
public static void process3(int[] arr, int L, int R) {
if (L >= R) {
return;
}
swap(arr, L + (int) (Math.random() * (R - L + 1)), R);
int[] equalArea = netherlandsFlag(arr, L, R);
process3(arr, L, equalArea[0] - 1);
process3(arr, equalArea[1] + 1, R);
}
// for test
public static int[] generateRandomArray(int maxSize, int maxValue) {
int[] arr = new int[(int) ((maxSize + 1) * Math.random())];
for (int i = 0; i < arr.length; i++) {
arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random());
}
return arr;
}
// for test
public static int[] copyArray(int[] arr) {
if (arr == null) {
return null;
}
int[] res = new int[arr.length];
for (int i = 0; i < arr.length; i++) {
res[i] = arr[i];
}
return res;
}
// for test
public static boolean isEqual(int[] arr1, int[] arr2) {
if ((arr1 == null && arr2 != null) || (arr1 != null && arr2 == null)) {
return false;
}
if (arr1 == null && arr2 == null) {
return true;
}
if (arr1.length != arr2.length) {
return false;
}
for (int i = 0; i < arr1.length; i++) {
if (arr1[i] != arr2[i]) {
return false;
}
}
return true;
}
// for test
public static void printArray(int[] arr) {
if (arr == null) {
return;
}
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i] + " ");
}
System.out.println();
}
// for test
public static void main(String[] args) {
int testTime = 500000;
int maxSize = 100;
int maxValue = 100;
boolean succeed = true;
for (int i = 0; i < testTime; i++) {
int[] arr1 = generateRandomArray(maxSize, maxValue);
int[] arr2 = copyArray(arr1);
int[] arr3 = copyArray(arr1);
quickSort1(arr1);
quickSort2(arr2);
quickSort3(arr3);
if (!isEqual(arr1, arr2) || !isEqual(arr2, arr3)) {
succeed = false;
break;
}
}
System.out.println(succeed ? "Nice!" : "Oops!");
}
}
1、netherlandsFlag方法说明
假设给个数组,[...3 1 2 6 3...]
L到R上,L是3,R是3,以arr[R]做划分值,所以以3划分
排序后是:1 2 3 3 6
返回等于区域左边界和右边界,就是左边3的位置和右边3的位置
第一次变动和第二次变动,示意图:
2、快排1.0版本说明
(1)在整个数组上,拿最后一个值为划分值,然后做partition
[<=x >x x]
(2)然后把x放到小于区的最后一个
[<=x x >x]
(3)排序的时候x可以不动,接下来左侧范围做递归
(4)每次递归可以至少搞定一个数
3、快排2.0版本说明
利用荷兰国旗问题
4、快排3.0版本说明
原来拿arr[R]做划分值,3.0版本随机选个位置i,拿i位置的数和R位置的数交换之后,以arr[R]做划分值
三、随机快排的时间复杂度
1、通过分析知道,划分值越靠近中间,性能越好:越靠近两边,性能越差
2、随机选一个数进行划分的目的就是让好情况和坏情况都变成概率事件
3、把每一种情况都列出来,会有每种情况下的时间复杂度,但概率都是1/N
4、那么所有情况都考虑,时间复杂度就是这种概率模型下的长期期望
时间复杂度O(N*logN),额外空间复杂度O(logN)都是这么来的。