数组的Partition与快速排序
数组的Partition
在数组中取一个数p,把<=p的数都放在左边,>p的数放在右边,数的排序可以是无序的。
流程:
在a[0]之前建一个<=区,i指向a[0]
若a[i] <= p, a[i]与<=区的右边一个数交换,<=区向右扩一个单位,i++
若a[i] > p, i++
升级:荷兰国旗问题
在数组中取一个数p,把<p的数都放在左边,=p的数放中间,>p的数放在右边,数的排序可以是无序的。
流程:
在a[0]之前建一个<=区,i指向a[0],a[a.length-1]之后建立一个>=区
若a[i] < p, a[i] < p, a[i]与<区后一个数交换,<区向右扩,i++
若a[i] > p,a[i]和>区前一个数交换,>区向左扩,i不变
若a[i] == p,i++
快速排序
快速排序的核心思想便是数组的partition,通过一轮partition之后,可以把一个数插到中间,这样每个数都会找到自己的位置,然后把左边和右边的数组分别递归进行partition,每个数字都可以找到自己的位置,最后数组便是有序的了。
没有任何改进的快排
用数组的最后一个数p作为划分,<=p的放数组的左边,>p的放数组的右边,一轮partition之后,p就存在与数组中间的某个位置,然后p不动,p左边的数组和右边的数组重复以上流程。
稍微改进的快排
用数组的最后一个数p作为划分,把<p的数都放在左边,=p的数放中间,>p的数放在右边,然后=p的区域不动,区域左边的数组和区域右边的数组继续重复以上流程。
进阶改进的快排
每次都随机把l到r上的任意一个数交换到最后,然后用这个数进行partition,最后p落在任意一个位置上的概率都是相等的,即1/n
累加的公式得出来,时间复杂度为O(n*logN),最坏情况和最好情况都已成概率事件了
工程上改进的快排
在quickSort这一步,当l < r - 60 时,使用插入排序。虽然插入排序时间复杂度为O(n^2),但是样本量少的时候速度飞快,没有时间瓶颈。
快速排序的时间复杂度分析
1.最坏O(n^2)
例子 1 2 3 4 5 6 7 8 9
以9作为划分,每次只搞定了最后一个数,每次都要承载O(n)的时间复杂度,所以总的时间复杂度为O(n^2)
2.最好O(n*logN)
每次都把p打到中点位置,左边和右边的长度都为n / 2
得出来的公式:T(N) = 2T(N / 2) + O(N)
其中O(N)为partition的时间复杂度
根据master公式得出时间复杂度为O(n)
由于按最差情况来估计时间复杂度,所以完全不改进的快排时间复杂度为O(n^2)。
空间复杂度
1.最坏情况 O(N)
2.最好情况 O(logN)
结论:经过概率的统计,最后收敛于O(logN)。
快速排序的Code
public class QuickSort_lijunru {
public static void quickSort(int[] arr) {
if (arr == null || arr.length < 1) {
return;
}
quickSort(arr, 0, arr.length - 1);
}
// 这是一个处理arr[l..r]的函数
// 返回等于区域的左边界和右边界,所以返回一个长度为2的数组
public static void quickSort(int[] arr, int left, int right) {
if (left < right) {
// 改进算法的重点,随机取一个数跟最右边的数交换,把这个数作为partition的基准
swap(arr, left + (int) (Math.random() * (right - left + 1)), right);
int[] p = partition(arr, left, right);
quickSort(arr, left, p[0] - 1);
quickSort(arr, p[1] + 1, right);
}
}
public static int[] partition(int[] arr, int left, int right) {
int less = left - 1;
int more = right;
/**
* 始终都是arr[left]与arr[right]进行比较
*/
while (left < more) {
if (arr[left] < arr[right]) {
swap(arr, ++less, left++);
} else if (arr[left] > arr[right]) {
swap(arr, --more, left);
} else {
left++;
}
}
/**
* 注意经过partition之后要把>区的第一个数和最后一个数进行交换
* 不能把less+1进行交换,交换之后>区就被污染了,只有more才能无缝切换
*/
// swap(arr, less + 1, right);
swap(arr, more, right);
return new int[] { less + 1, more };
}
public static void swap(int[] arr, int j, int k) {
int tmp = arr[j];
arr[j] = arr[k];
arr[k] = tmp;
}
public static void printArr(int[] arr) {
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
System.out.println();
}
public static void main(String[] args) {
int[] arr = {3, 4, 1, 8, 9, 6, 2};
printArr(arr);
quickSort(arr);
printArr(arr);
}
}
为什么工程上最常用快速排序
因为代码比起其他排序比较简洁,说明常数项低,所以跑得很快,而且时间复杂度为O(n*logN)。