经典快排:
给定一组数据,以这组数据的最后一个数为划分,假如最后一个数为x,则<=x放在左边,>x的放在右边
分别以左边和右边的最后一个数为划分,重复上述过程,这就是经典快排
荷兰国旗改进后的快排:
荷兰国旗问题:算法与数据结构笔试面试:荷兰国旗问题
还是以这组数据的最后一个数为划分,但是<x的放左边,==x的放中间,>x的放右边,再次从<x和>x的区域选最后一个数
重复上述过程
改进后的快排和经典快排的比较:
改进后的快排比较快,经典快排是每次只搞定一个数,也就是说,每次只能搞定x这一个数,比如说这组数据为:5,4,6,7,4,8,4,3,2,1,4
先是以4位区分,数据变为:3,2,1,4,4,4,4,5,6,7,8
只是搞定了下标为6位置的4,其他的4没有搞定,下次<=4的区域是以下标为5位置上的4作为划分,
但是改进后的快排第二次的划分是以下标为2位置的1作为划分,一下就把整个等于4的区域划分出来,不参与下次的比较,也就是说
当一组数据中有多个x时,经典快排只是搞定一个x,但是改进后的快排一次搞定所有x
两者时间复杂度差不多,但是在常数项上有一定差别。
经典快排存在的问题:
经典快排和数据状况有关,因为经典快排每次都是以最后一个位置的数作为划分,这样有可能导致划分的大于区域和
小于区域的数据规模不一致,如果每次小于区域和大于区域都有相同的规模,则时间复杂度可以做到O(n*logn),但是如果规模不一致
最坏情况,比如数据为:6,5,4,3,2,1 这就会导致只有大于区域没有小于区域,数据规模发生严重的偏差,时间复杂度可以做到O(N^2)
随机快排:
随机快排与经典快排唯一的不同,在选取划分的数据上不同,随机快排先在这组数据上随机选取一个数,然后将这个数和这组数据的最后
一个数交换,然后以最后这个数作为划分,这样的划分,不容易找出最坏情况,也不能确定划分的左边和右边规模的差异,此时,
随机快排的时间复杂度就变为一个概率事件,因为划分值的确定是等概率的,数组每一个位置的数据都有可能成为划分值,经过多次划分值
概率的累加,得到长期期望为O(nlogn),所以说随机快排的复杂度是一个长期期望的复杂度为O(nlogn),随机快排的额外空间复杂度为
O(logn),额外空间主要用在断点的位置,断点就是用来记录小于区域和大于区域的边界,因为先进行的是对小于x的数据放右边,
进行完小于区域的排放在进行大于放左边,那么进行完小于区域后,如何知道从什么位置开始进行大于区域呢,这就是设置断点,
存储边界位置,断点最好的情况下是每次递归都设在中间,这样额外空间复杂度为O(logn),最差情况下,比如数据为:6,5,4,3,2,1
第一次打在1,第二次打在2,如此下去,这样额外空间复杂度为O(n),因为划分值是随机的,所以额外空间复杂度也是一个概率,它的长期期望值为O(logn)
一个样本状况想要绕开它原始的数据状况,有两种方式,第一种用随机数打破它的随机状况,第二种哈希表
代码实现
public class QuickSort {
public static void quickSort(int[] arr) {
if(arr == null || arr.length < 2){
return;
}
quickSort(arr, 0, arr.length - 1);
}
public static void quickSort(int[] arr, int L, int R){
if (L < R) {
swap(arr, L + (int)(Math.random() * (R - L + 1)), R); //将这行代码注释掉,就是经典快排
int res[] = partition(arr, L, R);
quickSort(arr, L, res[0]);
quickSort(arr, res[1], R);
}
}
public static int[] partition(int[] arr, int L, int R){
int less = L - 1;
int more = R;
while(L < more){
if(arr[L] < arr[R]){
swap(arr, ++less, L++);
}else if (arr[L] > arr[R]) {
swap(arr, L, --more);
}else {
L++;
}
}
swap(arr, more++, R);
return new int[]{less, more};
}
public static void swap(int[] arr, int i, int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}