【算法】5 快速排序

(由于markdown的草稿箱十分“危险”所以就不存在草稿箱了,直接将这篇没有完成的博客发表出来了,今天晚些时候和明天会继续修改)

快速排序Quicksort由Tony Hoare在1962年发明。 这是一个分治算法,而且它就在原地排序。 所谓原地排序,就是指在原来的数据区域内进行重排,就像插入排序一般。而归并排序就不一样,它需要额外的空间来进行归并排序操作。为了在线性时间与空间内归并,它不能在线性时间内实现就地排序,原地排序对它来说并不足够。而快速排序的优点就在于它是原地的,也就是说,它很节省内存。

由于快速排序采用了分治算法,所以:

一、分解:本质上快速排序把数据划分成几份,所以快速排序通过选取一个关键数据,再根据它的大小,把原数组分成两个子数组:第一个数组里的数都比这个主元数据小或等于,而另一个数组里的数都比这个主元数据要大或等于。

技术分享

二、解决:用递归来处理两个子数组的排序。 (也就是说,递归地求上面图示中左半部分,以及递归地求上面图示中右半部分。)

三、合并:有了上面这两步就足够了,并不需要合并。因为上图中的两个数组分别排序好后整体也排序好了。

所以快速排序的主要思想是递归地划分。

当然最重要的是它的复杂度是线性的,也就是Θ(n)个划分的子程序。

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条件语句。

技术分享

刚开始时i等于p,j等于p+1。在这个循环中查找i下标的数据,如果它比x大,那就将其存放到“>=x”区域并将j加1后进行下一次循环。而如果它比x小,那就要做些动作来维持循环不变量了。将i的下标加1后将下标i对应的数据和下标j所对应的数据互换位置。然后再移动区域的界限并开始下一次循环。

那么这个算法在n个数据下的运行时间大约是O(n),因为它几乎把每个数都比较了一遍,而每个步骤所需的时间都为O(1)

技术分享

上面这幅图详细的描述了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) 

我们会这样初始调用快速排序:Quicksort(A,1,n) 。

分析

假设所有元素都不同。我们将会证明当你有重复元素时这些代码运行的状况并不好。

最坏情况分析:

1)输入的元素以及排序或逆向排序
2)每个划分的一边都没有元素

T(n)=T(0)+T(n?1)+\Theta(n)=Θ(1)+T(n?1)+Θ(n)=Θ(n?1)+Θ(n)=Θ(n2)

等差级数,就和插入排序一样。它并不比插入排序快。而快速排序仍旧是一个优秀的算法,这是因为在平均情况下它已经很高效。

我们为最坏情况花一个递归树。

技术分享

这是一课高度不平衡的递归树,图中左边的那些T(0)的运行时间都为Θ(1),而总共有n个。

所以算法的中运行时间为:

T(n)=Θ(n)+Θ(n2)=Θ(n2)

最优情况分析:

当Partition将数组分为n/2和n/2两个部分时是最高效的。此时有:

T(n)=2T(n/2)+Θ(n)=Θ(nlgn)

随机化快速排序的好处:

1)其运行时间不依赖与输入序列的顺序

2)无需对输入序列的分布做任何假设

3)没有 一种特别的输入会引起最差的运行情况

4)最差的情况由随机数产生器决定

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。