《算法图解》-4 快速排序

本文属于《图解算法》系列。

  一 分而治之

    分而治之(devide and conquer)是递归思路是常见的解决方式。

  作者以划分土地为例子,介绍这个思路:

换个思路,这是数学上典型的求两个数的最大公约数(GCD)的问题。

这里重申一下D&C的工作原理:

(1) 找出简单的基线条件;

(2) 确定如何缩小问题的规模,使其符合基线条件。

下面是Java分贝用递归跟非递归的方式实现。

public class EuclideanTest {
	
	public static int Recursion(int m,int n){
		if (n==0){
			return m;
		}
		return Recursion(n,m%n);
	}

	public static void main(String[] args) {
		int res = Recursion(1680,640);
		int res2 = Recursion2(1680,640);
		System.out.println("res="+res+":"+res2);
		
	}
	
	public static int Recursion2(int m,int n){
		while(n!=0){
			int tmp = m%n;
			m = n; 
			n = tmp;
		}
		return m;
		
	}

}

作者还举了一个数组求和的例子:

public class SumTest {
	
	public static int sum(int[] nums){
		
		if(nums.length==1){
			return nums[0];
		}else{
			int[] tmp = new int[nums.length-1];
			for(int i=1;i<=nums.length-1;i++){
				tmp[i-1]= nums[i];
			}
			return nums[0]+ sum(tmp);
		}
		
	}
	

	public static void main(String[] args) {
		int [] test = {2,4,6};
		int res= sum(test);
		System.out.println(res);
	}

}

二 快速排序

  对于一个数组,快速排序的步骤如下:

(1) 选择基准值。

(2) 将数组分成两个子数组:小于基准值的元素和大于基准值的元素。

(3) 对这两个子数组进行快速排序。

好吧,对于Java版本的实现要复杂些,起码数组操作没这么灵活:

   

public static void quickSort(int[] arrs,int left,int right){
	
		if(left<right){
			 int temp,i=left,j=right;
	           temp=arrs[right];//基准值
	           while(i<j){
	               //寻找左边第一个大于基准值的下标
	               while(arrs[i]<=temp&&i<j)i++;
	               //赋值给right
	               if(i<j)arrs[j--]=arrs[i];
	               //寻找右边第一个小于基准值的下标
	               while(arrs[j]>=temp&&i<j)j--;
	               //赋值给left
	               if(i<j)arrs[i++]=arrs[j];
	           }
	           arrs[i]=temp;
			//递归小的
			quickSort(arrs,left,j-1);
			//递归大的
			quickSort(arrs,j+1,right);
			
		}		
		
	}
	

	public static void main(String[] args) {
		int [] test = {10,5,2,3};
		quickSort(test,0,test.length-1);
		System.out.println(JSON.toJSON(test));
	}

以测试的【10,5,2,3】为例,基准变量取最右侧的一位,是3

调用过程如下:左侧找到第一个比基准值大的数,赋值给右侧的right,

      10,5,2,10

再寻找右侧第一个比基准小的数,赋值给左侧的left

  2,5,2,20

此时,一轮下来,i=1,j=2 复合条件,temp=3,继续while循环

重复上面的步骤,左侧有比temp3大的5,处理后变成2,5,5,10

                            右侧没有复合条件的,跳过。

i=1,j=1.不符合while条件,退出。

arrs[i] =temp;

          2,3,5,10

在递归一次,左侧小的【0,0】不符合条件,退出。

右侧【2,3】,left=2,right=3,基准值取出来为10,自己跟一下更清楚。

为了方便理解,在阿里云上找了个图片方便理解。

ecf8c9a0626b3c623276c4fe6770d87e3b24510c

 三 大O运行时间

    数据不准确,置为展示时间曲线特性

还有一种名为合并排序(merge sort的排序算法,其运行时间为O(n log n),比选择排序快 得多!快速排序的情况比较棘手,在最糟情况下,其运行时间为O(n2 )

  之前说的通常会忽略掉大O里面的常量部分,但有时候,常量的影响可能很大,对快速查找和合并查找来说就是如此。快速查找的常量合并查找小,因此如果它们的运行时间都为O(n log n),快速查找的速度将更快。实际上,快速查 找的速度确实更快,因为相对于遇上最糟情况,它遇上平均情况的可能性要大得多。

最糟情况:

最佳情况:

最佳情况:

第一个示例展示的是最糟情况,而第二个示例展示的是最佳情况。在最糟情况下,栈长为 O(n),而在最佳情况下,栈长为O(log n)。现在来看看栈的第一层。你将一个元素用作基准值,并将其他的元素划分到两个子数组中。 这涉及数组中的全部8个元素,因此该操作的时间为O(n)。在调用栈的第一层,涉及全部8个元素, 但实际上,在调用栈的每层都涉及O(n)个元素。

      在这个示例中,层数为O(log n)(用技术术语说,调用栈的高度为O(log n)),而每层需要的 时间为O(n)。因此整个算法需要的时间为O(n) * O(log n) = O(n log n)。这就是最佳情况。

在最糟情况下,有O(n)层,因此该算法的运行时间为O(n) * O(n) = O(n2 )

知道吗?最佳情况也是平均情况。只要你每次都随机地选择一个数组元素作为基准值,快速排序的平均运行时间就将为O(n log n)。快速排序是最快的排序算法之一,也 是D&C典范。

4 小结

D&C将问题逐步分解。使用D&C处理列表时,基线条件很可能是空数组或只包含一个元 素的数组。

实现快速排序时,请随机地选择用作基准值的元素。快速排序的平均运行时间为O(n log n)

O表示法中的常量有时候事关重大,这就是快速排序比合并排序快的原因所在。

比较简单查找和二分查找时,常量几乎无关紧要,因为列表很长时,O(log n)的速度比O(n) 快得多。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值