【算法】5 传说中的快排是怎样的
什么是快速排序
快速排序简介
快速排序(英文名:Quicksort,有时候也叫做划分交换排序)是一个高效的排序算法,由Tony Hoare在1959年发明(1961年公布)。当情况良好时,它可以比主要竞争对手的归并排序和堆排序快上大约两三倍。这是一个分治算法,而且它就在原地排序。
所谓原地排序,就是指在原来的数据区域内进行重排,就像插入排序一般。而归并排序就不一样,它需要额外的空间来进行归并排序操作。为了在线性时间与空间内归并,它不能在线性时间内实现就地排序,原地排序对它来说并不足够。而快速排序的优点就在于它是原地的,也就是说,它很节省内存。
引用一张来自维基百科的能够非常清晰表示快速排序的示意图如下:
快速排序的分治思想
由于快速排序采用了分治算法,所以:
一、分解:本质上快速排序把数据划分成几份,所以快速排序通过选取一个关键数据,再根据它的大小,把原数组分成两个子数组:第一个数组里的数都比这个主元数据小或等于,而另一个数组里的数都比这个主元数据要大或等于。
二、解决:用递归来处理两个子数组的排序。 (也就是说,递归地求上面图示中左半部分,以及递归地求上面图示中右半部分。)
三、合并:因为子数组都是原址排序,所以不需要合并操作,通过上面两步后数组已经排好序了。
所以快速排序的主要思想是递归与划分。
如何划分
当然最重要的是它的复杂度是线性的,也就是
Partition(A,p,q) // A[p,..q]
1 x=A[p] // pivot=A[p] 主元
2 i=p
3 for j=p+1 to q
4 do if A[j]<=x
5 then i=i+1
6 exch A[i]<->A[j]
7 exch A[p]<->A[i]
8 return i // i pivot
这就是划分的伪代码,基本的结构就是一个for循环语句,中间加上了一个if条件语句,它实现了对子数组
刚开始时
那么这个算法在n个数据下的运行时间大约是
上面这幅图详细的描述了Partition过程,每一行后也加了注释。
将递归的思想作用于划分上
有了上面这些准备工作,再加上分治的思想实现快速排序的伪代码也是很简单的。
Quicksort(A,p,q)
1 if p<q
2 then r=Partition(A,p,q)
3 Quicksort(A,p,r-1)
4 Quicksort(A,r+1,q)
为了排序一个数组A的全部元素,初始调用时
快速排序的算法分析
相信通过前面的诸多实践,大家也发现了快速排序的运行时间依赖于Partition过程,也就是依赖于划分是否平衡,而归根结底这还是由于输入的元素决定的。
如果划分是平衡的,那么快速排序算法性能就和归并排序一样。
如果划分是不平衡的,那么快速排序的性能就接近于插入排序。
怎样是最坏的划分
1)输入的元素已经排序或逆向排序
2)每个划分的一边都没有元素
也就是说当划分产生的两个子问题分别包含了n-1个元素和0个元素时,快速排序的最坏情况就发生了。
这是一个等差级数,就和插入排序一样。它并不比插入排序快,因为当同样是输入元素已经逆向排好序时,插入算法的运行时间为
我们为最坏情况画一个递归树。
这是一课高度不平衡的递归树,图中左边的那些
所以算法的中运行时间为:
最坏划分的算法分析
通过上面的图示我们知道了在最坏情况下快速排序的复杂度是
当输入规模为n时,时间
除去主元后,在Partition函数中生成的两个子问题的规模的和为n-1,所以r的规模才是0到n-1。
假设
1)而
于是有
最终因为我们可以选择一个足够大的
2)
于是有
同样我们也可以选择一个足够小的
综上这两点得到
怎样是最好的划分
当Partition将数组分为
怎样是平衡的划分
快速排序的平均运行时间更接近于其最好情况,而非最坏情况。
此处有一个经典的示例,将数组按
其中此时的递归式是:
这里依旧通过递归树来观察一番。
因为每次都减少十分之一,需要减多少次才能达到n呢,也恰好也是以10为底对数的定义。所以左侧的高度为
所有那些叶子加在一起也只有
其实
只要划分是常数比例的,算法的运行时间总是
随机化快速排序
随机算法的思想
在前面分析快速排序的平均情况性能时,是建立在输入数据的所有排列都是等概率的条件下的,但在实际工程中往往不会总出现这种良好的情况。
在【算法】3 由招聘问题看随机算法中我们介绍了随机算法,它使得对于所有的输入都有着较好的期望性能,因此随机化快速排序在有大量数据输入的情况下是一种更好的排序算法。
以下是随机化快速排序的好处:
1)其运行时间不依赖与输入序列的顺序
2)无需对输入序列的分布做任何假设
3)没有 一种特别的输入会引起最差的运行情况
4)最差的情况由随机数产生器决定
随机抽样技术
现在我们来使用一种叫做随机抽样(random sampling)的随机化技术,使用该技术就不再始终采用A[p]作为主元,而是从A[p…q]中随机选择一个元素作为主元。
为了达到这一目的,首先将
通过对序列
因为主元元素是随机选择的,我们可以期望在平均情况下对输入数组的划分是比较均衡的。所以对前面的两份伪代码做如下修改:
RANDOMIZED-PARTITION(A,p,q)
1 i=RANDOM(p,q)
2 exchange A[p] with A[i]
3 return PARTITION(A,p,q)
RANDOMIZED-QUICKSORT(A,p,q)
1 if p<q
2 r=RANDOMIZED-PARTITION(A,p,q)
3 RANDOMIZED-QUICKSORT(A,p,r-1)
4 RANDOMIZED-QUICKSORT(A,r+1,q)
有了随机抽样技术后再也不用担心快速排序遇到最坏划分的情况啦,所以说随机化快速排序的期望运行时间是
感谢您的访问,希望对您有所帮助。 欢迎大家关注、收藏以及评论。
为使本文得到斧正和提问,转载请注明出处:
http://blog.csdn.net/nomasp
版权声明:本文为 NoMasp柯于旺 原创文章,如需转载请联系本人。