左程云算法通关023 随机快速排序

本文详细介绍了经典随机快速排序和荷兰国旗问题优化后的快速排序算法,重点讨论了时间复杂度和空间复杂度,以及如何通过随机选择枢轴避免最差情况,使得平均时间复杂度达到O(nlogn),空间复杂度为O(logn)。
摘要由CSDN通过智能技术生成

前置知识: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就可以通过。

  • 9
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值