前置知识:007 时间复杂度和空间复杂度——分析随机行为时间复杂度的部分
一、经典随机快速排序流程讲解
经典快速排序过程已经在数据结构里分析过了,主要是随机选择一个数作为枢轴,同时将数组进行划分的两个过程。
我们主要再来研究下划分的过程,假定选的随机数是4,待排序列为4 2 4 6 9 5 3
分两种情况讨论,利用双指针法,两个指针分别为a和i:
[i]<=4:i和a对应的值交换,然后a++,i++,也就是不断扩大≤4的范围
[i]>4 : i++即可
public static void quickSort(int l,int r){
//l==r只有一个数不用管
//l>r 范围无效
if(l>=r)
return;
//随机这一下,常数时间比较大
//但只有随机,才能把时间复杂度从概率上收敛到O(nlogn)
int x=arr[l+(int)(Math.random()*(r-l+1))];
//r-l+1表示我从l到r的长度。
//Math.random()返回一个在0.0(包括)-1.0(不包括)double类型的随机数
//l+后面的那个整数表明随机选一个从l到r的随机数
int mid=partition(l,r,x);
quickSort(l,mid-1);
quickSort(mid+1,r);
}
//已知数组[l.....r]范围上一定有这个x
//划分数组,<=x在左边>x在右边,并且x一定在<=x的最右边
public static int partition(int l,int r,int x){
//a:arr[l....a-1]范围是<=x的范围
//xi:记录在<=x区域上任何一个x的位置
int a=l;int xi=0;
for(int i=l;i<=r;i++){
if(arr[i]<=x){
swap(arr,a,i);
if(arr[a]==x)
xi=a;
a++;
}
}
//<=x >x
//l...a-1 a...r
swap(arr,xi,a-1);
//交换是为了确保<=x最后的数字是x
return a-1;
}
二、荷兰国旗问题优化随机快速排序流程讲解
荷兰国旗问题优化过程:在当前范围选择一个数字X,利用荷兰国旗问题进行数组的划分,<x,=x,>x,接着对<x,>x这两个部分重复这个过程。
我们将区域分为三个部分,<,>,=三部分分别处理,这样就可以避免重新处理等于x的部分。同时这次我们需要三个指针来处理了。a指向开始位置,b指向终点位置,i 从头开始遍历
小于:i,a交换值,i++,a++
大于:i++
等于:i,b交换值,b--,i不变
请自己在草稿纸上分析下为什么i要做这些变化。
最后a指向的一定是<x的右边边界之外,b指向的是>x的左边边界之外,所以在递归时left-1,right+1
荷兰国旗问题的优化点:选出一个数字x,数组在划分时会搞定所有值=X的部分。
// 随机快速排序改进版(推荐)
public static void quickSort2(int[] arr, int l, int r) {
if (l >= r) {
return;
}
int x = arr[l + (int) (Math.random() * (r - l + 1))];
partition2(arr, l, r, x);
// 为了防止底层的递归过程覆盖全局变量
// 这里用临时变量记录first、last
int left = first;
int right = last;
quickSort2(arr, l, left - 1);
quickSort2(arr, right + 1, r);
}
//荷兰国旗问题
public static int first,last;
public static void partition2(int arr[],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(arr,i,first);
first++;
i++;
}
else if(arr[i]>x){
swap(arr,last,i);
last--;
}
}
}
三、时间复杂度分析
随机行为作为算法的重要枢纽下,不能用最差过程作为算法的时间复杂度,而应该用时间复杂度的期望来算。
最差情况是,假设数组中没有重复数字,数组为1,2,3,4,5,6,7,8,9.我们每次随机都随机到了最左侧或者最右侧(也就是随机行为变成了固定行为),此时空间复杂度为O(n),时间复杂度为O(n^2),9个数每次划分,划分总共遍历次数为1+2+3+4+...+9。
最好情况为,每次划分都靠近中央,大致把数组划分为了均匀的两部分,这时可以利用master公式了,T(n)=2*T(n/2)+O(n),故时间复杂度为O(nlogn)。空间复杂度为O(logn).
故随机快速排序时间复杂度为O(nlogn),空间复杂度为O(logn)。证明见算法导论7.4.2的章节。
四、例题
力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
class Solution {
public static int[] sortArray(int[] nums) {
if (nums.length > 1) {
quickSort2(nums, 0, nums.length - 1);
}
return nums;
}
// 随机快速排序经典版(不推荐)
public static void quickSort1(int[] arr, int l, int r) {
if (l >= r) {
return;
}
// 随机这一下,常数时间比较大
// 但只有这一下随机,才能在概率上把快速排序的时间复杂度收敛到O(n * logn)
int x = arr[l + (int) (Math.random() * (r - l + 1))];
int mid = partition1(arr, l, r, x);
quickSort1(arr, l, mid - 1);
quickSort1(arr, mid + 1, r);
}
// 已知arr[l....r]范围上一定有x这个值
// 划分数组 <=x放左边,>x放右边,并且确保划分完成后<=x区域的最后一个数字是x
public static int partition1(int[] arr, 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(arr, a, i);
if (arr[a] == x) {
xi = a;
}
a++;
}
}
swap(arr, xi, a - 1);
return a - 1;
}
public static void swap(int[] arr, int i, int j) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
// 随机快速排序改进版(推荐)
public static void quickSort2(int[] arr, int l, int r) {
if (l >= r) {
return;
}
int x = arr[l + (int) (Math.random() * (r - l + 1))];
partition2(arr, l, r, x);
// 为了防止底层的递归过程覆盖全局变量
// 这里用临时变量记录first、last
int left = first;
int right = last;
quickSort2(arr, l, left - 1);
quickSort2(arr, right + 1, r);
}
//荷兰国旗问题
public static int first,last;
public static void partition2(int arr[],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(arr,i,first);
first++;
i++;
}
else if(arr[i]>x){
swap(arr,last,i);
last--;
}
}
}
}
我们可以用quicksort1和2进行比较,1是通不过的,不优化肯定有非常难受的案例,但是2就可以通过。