1.普通快速排序
算法思想:
1.使用递归来实现快排
2.每次选择数组l到r中的一个数,把他放到正确的位置上,左边都是<=它的的,右边都是比它大的
3.如何实现2这个方法?
假设我们挑选的数是x
设置指针a和i,最开始都指向l位置
如果i位置的数小于等于x ,交换i和a位置的数 a++ i++
如果i位置的数大于x ,交换i和a位置的数 i++
(上面的意思其实a就是一个边界,a的左边全是比x小或等于的)
代码如下:
// 随机快速排序经典版(不推荐)
// 甚至在洛谷上测试因为递归开太多层会爆栈导致出错
public static void quickSort1(int l, int r) {
// l == r,只有一个数
// l > r,范围不存在,不用管
if (l >= r) {
return;
}
// 随机这一下,常数时间比较大
// 但只有这一下随机,才能在概率上把快速排序的时间复杂度收敛到O(n * logn)
// l......r 随机选一个位置,x这个值,做划分
int x = arr[l + (int) (Math.random() * (r - l + 1))];
int mid = partition1(l, r, x);
quickSort1(l, mid - 1);
quickSort1(mid + 1, r);
}
// 已知arr[l....r]范围上一定有x这个值
// 划分数组 <=x放左边,>x放右边
// 并且确保划分完成后<=x区域的最后一个数字是x
public static int partition1(int l, int r, int x) {
// a : arr[l....a-1]范围是<=x的区域
// xi : 记录在<=x的区域上任何一个x的位置,哪一个都可以
int a = l, xi = 0;
for (int i = l; i <= r; i++) {
if (arr[i] <= x) {
swap(a, i);
if (arr[a] == x) {
xi = a;
}
a++;
}
}
swap(xi, a - 1);
return a - 1;
}
public static void swap(int i, int j) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
2.随机快速排序改进版
算法思想:
刚才我们每次挑选一个数放到正确的位置上,划分也是左边比他小或等于,右边比他大。
但是如果我们每次能把相等的都找出来放到正确位置上,这样左边都是比他小的,右边都是比他大的。跑递归时候会大大提高效率。
所以我们采用a b i三个指针 a指针左边都是比他小的 b指针右边都是比他大的。a b 指针之间就相等的。
代码如下:
// 随机快速排序改进版(推荐)
// 可以通过所有测试用例
public static void quickSort2(int l, int r) {
if (l >= r) {
return;
}
// 随机这一下,常数时间比较大
// 但只有这一下随机,才能在概率上把快速排序的时间复杂度收敛到O(n * logn)
int x = arr[l + (int) (Math.random() * (r - l + 1))];
partition2(l, r, x);
// 为了防止底层的递归过程覆盖全局变量
// 这里用临时变量记录first、last
int left = first;
int right = last;
quickSort2(l, left - 1);
quickSort2(right + 1, r);
}
// 荷兰国旗问题
public static int first, last;
// 已知arr[l....r]范围上一定有x这个值
// 划分数组 <x放左边,==x放中间,>x放右边
// 把全局变量first, last,更新成==x区域的左右边界
public static void partition2(int l, int r, int x) {
first = l;
last = r;
int i = l;
while (i <= last) {
if (arr[i] == x) {
i++;
} else if (arr[i] < x) {
swap(first++, i++);
} else {
swap(i, last--);
}
}
}
普通快速排序,时间复杂度O(n^2),额外空间复杂度O(n)
随机快速排序,时间复杂度O(n * logn),额外空间复杂度O(logn)