今天分享的是关于快速排序的思路以及三种版本的迭代优化
我们先来介绍partition问题 多图预警!! 所有的算法都经过对数器核对,可以放心使用
已知一个数组arr,要求我们以随机的一个数作为标准把数组分为以下两个区域,小于等于区、大于区,可以不用考虑各区数字的排序 下例我们假设这个数为4
我们设置一个小于等于区(下称≤区),它的起始位置是-1;再设置一个变量index,它指向的是第一个数
然后我们从左往右遍历,当index 大于 arr.length-1 时跳出循环,有以下两种情况
- 如果arr[i] <= num 则当前数(arr[i])和 “≤区”的下一个数交换,“≤区”右扩,i++
- 如果arr[i] > num 则 i++
我们来进行下一步分析
- 3是小于4的,符合第一个逻辑,则 arr[i] 即 arr[0] 和 “≤区”的下一个数进行交换,即自己和自己交换,然后 i++ ,“≤区”右扩。此时 i = 1,“≤区”在0位置
- 5是大于4的,符合第二个逻辑,则 i++。此时 i = 2,“≤区”在0位置
- 2是小于4的,符合第一个逻辑,则 arr[i] 即 arr[2] 和 “≤区”的下一个数进行交换,即arr[1]和arr[2]进行交换,然后 i++ ,“≤区”右扩。此时 i = 3,“≤区”在1位置
- 4是等于4的,符合第一个逻辑,则 arr[i] 即 arr[3] 和 “≤区”的下一个数进行交换,即arr[2]和arr[3]进行交换,然后 i++ ,“≤区”右扩。此时 i = 4,“≤区”在2位置
- 8是大于4的,符合第二个逻辑,则 i++。此时 i = 5,“≤区”在2位置
- 1是小于4的,符合第一个逻辑,则 arr[i] 即 arr[5] 和 “≤区”的下一个数进行交换,即arr[3]和arr[5]进行交换,然后 i++ ,“≤区”右扩。此时 i = 6,“≤区”在3位置
- 7是大于4的,符合第二个逻辑,则 i++。此时 i = 7,“≤区”在3位置
- 4是等于4的,符合第一个逻辑,则 arr[i] 即 arr[7] 和 “≤区”的下一个数进行交换,即arr[4]和arr[7]进行交换,然后 i++ ,“≤区”右扩。此时 i = 8,跳出循环,“≤区”在4位置
可以看到,我们成功的分离出了这两个数组,这个方法可以运用到很多问题。partition的代码实现如下,为了方便起见,我们选的的基准数是每一个数组最右边的数,大家可以仔细体会下面的代码。
public static int partition(int[] arr, int left, int right) {
if (left > right) {
return -1;
}
if (left == right) {
return left;
}
int less = left - 1;
int index = left;
while (index < right) {
if (arr[index] <= arr[right]) {
swap(arr, index, ++less);
}
index++;
}
swap(arr, ++less, right);
return less;
}
public static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
仔细观察,可以发现我们的partition方法是返回了“≤区”所在的位置的,这个位置的值实际上就是我们已经排好序的位置,我们根据这个位置,对其左边的数组和右边的数组再进行partition操作,就是我们快排的第一个版本了。
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 left, int right) {
if (left >= right) {
return;
}
int m = partition(arr, left, right);
process1(arr, left, m - 1);
process1(arr, m + 1, right);
}
接下来我们介绍荷兰国旗netherlandsFlag划分问题,它可以进一步改进我们的快排
已知一个数组arr,要求我们以随机的一个数作为标准把数组分为以下三个区域,小于区、等于区、大于区,可以不用考虑各区数字的排序 下例我们假设这个数为4
类似的,我们设置一个小于区(下称”<区“),它的起始位置是-1,一个大于区(下称”>区“),它的起始位置是 arr.length 再设置一个变量index,它指向的是第一个数
我们假定指定的基准数是4
然后我们从左往右遍历,当index 大于 arr.length-1 时跳出循环,有以下三种情况
- 如果arr[i] < num 则当前数(arr[i])和”<区“的下一个数交换,“<区”右扩,i++
- 如果arr[i] > num 则 当前数(arr[i])和”>区“的前一个数交换,“>区”左扩,i停在原地(要点)
- 如果arr[i] == num 则 i++
大家可以根据上面的规则在纸上画一画~~~一定要耐心画一画!!得出结果的时候会觉得很舒服
) 3 5 2 4 8 1 7 4 (
3 ) 5 2 4 8 1 7 4 ( 判断条件:3<4
3 ) 4 2 4 8 1 7 ( 5 判断条件:5>4
3 ) 4 2 4 8 1 7 ( 5 判断条件:4=4
3 2 ) 4 4 8 1 7 ( 5 判断条件:2<4
3 2 ) 4 4 8 1 7 ( 5 判断条件:4=4
3 2 ) 4 4 7 1 ( 8 5 判断条件:8>4
3 2 ) 4 4 1 ( 7 8 5 判断条件:7>4
3 2 1 ) 4 4 ( 7 8 5 判断条件:1<4
可以看到,我们成功的分离出了这三个数组,netherlandsFlag的代码实现如下,为了方便起见,我们选的的基准数同样选择的是每一个数组最右边的数。
/**
* arr[L ...... R] 荷兰国旗划分问题 以arr[right]为划分值
* <arr[R] ==arr[R] >arr[R]
* 空间复杂度O(1) 时间复杂度O(n)
*/
public static int[] netherlandsFlag(int[] arr, int left, int right) {
if (left > right) {
return new int[]{-1, -1};
}
if (left == right) {
return new int[]{left, right};
}
// <区 有边界
int less = left - 1;
//因为我们以arr[right]为界限 所以暂时不动
// >区 左边界
int more = right;
int index = left;
while (index < more) {
if (arr[index] == arr[right]) {
index++;
} else if (arr[index] < arr[right]) {
swap(arr, index++, ++less);
} else if (arr[index] > arr[right]) {
swap(arr, index, --more);
}
}
//L......Less Less+1......more-1 more......R-1 R
swap(arr, more, right);
return new int[]{less + 1, more};
}
一定要注意好边界问题,仔细想一想。
此时我们可以注意到,netherlandsFlag返回的是等于区域的数组范围,与partition问题同样的,这中间的数组是已经排好序了的数组,我们可以继续用netherlandsFlag对其左边数组和右边数组排序,就可以对我们快排效率进一步优化(因为中间少区分了一个范围上的数)。下面是快排第二版:
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 left, int right) {
if (left >= right) {
return;
}
int[] equalArea = netherlandsFlag(arr, left, right);
process2(arr, left, equalArea[0] - 1);
process2(arr, equalArea[1] + 1, right);
}
快排第三版,就是我们不再选择最右侧的数作为基准,而是随机选的一个数,这样可以让时间复杂度变成O(n*logn),这是一个数学上的期望问题,感兴趣的可以去查阅相关资料。
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 left, int right) {
if (left >= right) {
return;
}
swap(arr, (int) (left + (Math.random() * (right - left + 1))), right);
int[] equalArea = netherlandsFlag(arr, left, right);
process3(arr, left, equalArea[0] - 1);
process3(arr, equalArea[1] + 1, right);
}
以上就是我们快排的三个迭代版本!
以下再给出我之前在网上看到的一个版本和对数器(校验算法是否正确的),测试了十万次,保证算法一定是正确的!
public static void quickSort4(int[] arr, int left, int right) {
if (left < right) {
int base = arr[left];
int i = left;
int j = right;
while (i < j) {
while (arr[i] < base && i < right) {
i++;
}
while (arr[j] > base && j > left) {
j--;
}
if (i <= j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
i++;
j--;
}
}
if (left < j) {
quickSort4(arr, left, j);
}
if (right > i) {
quickSort4(arr, i, right);
}
}
}
/**
* test
*/
public static int[] generateRandomArray(int maxSize, int maxValue) {
int[] arr = new int[(int) (Math.random() * (maxSize + 1))];
for (int i = 0; i < arr.length; i++) {
arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random());
}
return arr;
}
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;
}
public static boolean isEqual(int[] arr1, int[] arr2) {
if (arr1.length != arr2.length) {
return false;
}
for (int i = 0; i < arr1.length; i++) {
if (arr1[i] != arr2[i]) {
return false;
}
}
return true;
}
public static void printArray(int[] arr) {
if (arr == null) {
return;
}
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
System.out.println();
}
public static void main(String[] args) {
int testTime = 100000;
int maxSize = 20;
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);
int[] arr4 = copyArray(arr1);
quickSort1(arr1);
quickSort2(arr2);
quickSort3(arr3);
quickSort4(arr4, 0, arr4.length - 1);
if (!isEqual(arr1, arr2) || !isEqual(arr2, arr3)) {
succeed = false;
printArray(arr1);
printArray(arr2);
printArray(arr3);
break;
}
if (!isEqual(arr1, arr4)) {
succeed = false;
printArray(arr1);
printArray(arr4);
break;
}
}
System.out.println(succeed ? "bingo" : "fail");
}
如有不当肯定大家指出,谢谢~