那些年快速排序、归并排序、堆排序的明争暗斗……

1. 当排除数据的干扰性之后,若问,当排序的数组规模在10左右时,快速排序、归并排序、堆排序的运行时间是怎么样的?

大多数人答曰:快速排序最快,归并次之,堆排序最慢。

        好吧,基本应该是这样的。

 

2. 若问,当排序的数组规模在10左右且元素基本有序时,快速排序、归并排序、堆排序的运行时间是怎么样的?

一部分人答曰:归并排序最快,快速排序次之,堆排序最慢。

        我想我会怎么回答呢?我不知道。

        当元素有序的时候,之所以说归并排序快,就是因为比较次数少了,减少到了逼近nlog(n)/2的规模,而此时对于快速排序和堆排序并不是最有利的,因为它们的比较次数还会是在O(nlogn)的规模,可能这样说也不太对(尤其是对于快速排序),但是比较次数比归并排序多应该是必然的。(根据大量程序的统计结果表明,一般快速排序的比较次数是归并排序的2倍!)但是,此时,只有10的规模,程序的运行时间跟算法的比较次数真的没有太大的关系。

 

 

3. 当排除数据的干扰性之后,若问,当排序的数组规模在10000左右时,快速排序、归并排序、堆排序的运行时间是怎么样的?

部分人答曰:快速排序最快、归并排序次之、堆排序最慢。

        快速排序之快大家早有耳闻,所以必须当仁不让。为什么归并次之呢?是啊,刚刚明明还说了一般快速排序的比较次数是归并排序的2倍,为什么快速排序反而比归并排序运行起来快呢?这得从程序的结构上来分析,归并比较次数确实少,但是在while循环时,要判断是否出界(而快速排序的while循环基本上是永远为true的,基本不必考虑这个问题,但是要考虑另一个较小的问题,下面再说),至少需要在此多做O(nlogn)这个规模的次数(我对这个具体数字也不是很确定,呵呵)比较,而且,归并排序额外占用了一个同等规模的数组,还要把数据拷贝回去,所以导致了速度明显慢于快速排序。

       那么,此时,归并排序和堆排序又该谁快呢?我试着写了一个C#的归并排序和堆排序,采用随机数生成了个10000大小的数组,发现,居然堆排序居然比归并排序快!当然,也许并不是如此,只是实验的偶然性,我并能保证我写的程序的其他条件是“平等的”。但是我宁愿相信归并排序还是比堆排序更快的。有时候,我们不必纠结于结果,而是应当了解这其中的缘由,既然已经发生了这样的情况,到底会是什么原因呢?我想会不会跟计算机体系结构有关呢?是不是因为Cache(高速缓冲存储器)呢?归并排序额外占用了一个同等规模的数组,所以空间复杂度是O(n),而堆排序理论上只需要一个一个比较时的临时空间,所以空间复杂度是常数级别O(1)。我们这里所说的快速排序、归并排序、堆排序都是内排序,数据都是在内存中完成比较,而为了体现“程序的局部性”原则,一般这些数据都会同时写在Cache中,虽然内存和Cache之间的速度是极快的,但是这样的间隙还是可以因为数据量的增大而被放大的。

 

 

4. 当排除数据的干扰性之后,若问,当排序的数组规模在10000000左右时,快速排序、归并排序、堆排序的运行时间是怎么样的?

这时候大部分都会答曰:堆排序最快、快速排序次之、归并排序最慢。

       说到此,似乎并没有什么话可说的了,确实,各种资料参考书上、各种网站链接上,都有“堆排序适合海量大数据的排序”此类云云。但是,“适合”难道就一定是最快的吗?

       我没做过实验,我不敢说,10000000规模的int数组其存储容量虽说还不大,但是也需要大概40M的空间,让自己有限的内存来玩这些事情有些捉襟见肘。我们也不必在乎到底是谁最快,我们的目标还是在于理解一些东西,即使我们很浅显。

 

       这个时候我们确实有理由,而且不少人的实验也证明了的,堆排序确实开始“快”起来了,而且已经超过了快速排序。那么我们得来分析一下为什么快速排序不再“快”起来了。关于这个就得从“怎么样才算是快的要素”说起。我们学过的一般的算法中,时间复杂度为O(logn)的算法应该算是最快的,比如二分搜索。所以,我们任何的问题如果可以映射成二分搜索类的,那么这个解决方案是相当完美的。二分搜索之所以好,是因为它每次几乎可以排除当前规模一半的可能性,使得解决过程中少走弯路。很显然,归并排序、堆排序并不符合这样的特征,而快速排序就有些许这样的特征。快速排序的核心就是轴元素pivot的选取,将所有大于轴元素的移到左边,其余移到右边。这里我引用了一下别人比较专业的话。快速排序的第一次比较就是将一个元素和轴元素比较,这个时候显而易见的是,“大于”和“小于”的可能性各占一半。然而,快速排序的第二次比较就不那么高明了:我们不妨令轴元素为pivot,第一次比较结果是a1<pivot,那么可以证明第二次比较a2也小于pivot的可能性是2/3!这容易证明:如果a2>pivot的话,那么a1,a2,pivot这三个元素之间的关系就完全确定了——a1<pivot<a2,剩下来的元素排列的可能性我们不妨记为P(不需要具体算出来)。而如果a2<pivot呢?那么a1和a2的关系就仍然是不确定的,也就是说,这个分支里面含有两种情况:a1<a2<pivot,以及a2<a1<pivot。对于其中任一种情况,剩下的元素排列的可能性都是P,于是这个分支里面剩下的排列可能性就是2P。所以当a2<pivot的时候,还剩下2/3的可能性需要排查。所以,快速排序并不像二分搜索那样完美,它每次排除的可能性越来越小,而且反复交换之中带来的时间开销也逐渐显现,所以,有理由相信,它快不起来。

       为什么堆排序此时可堪大任呢?首先堆排序是一个线程稳定的,存取稳定的(虽然是不稳定排序算法),在堆排序的过程中,基本不太会发生对程序具有Fatal Destroy的事情来,这一点归并排序就不行,这个规模下的归并排序可能会发生内存溢出的问题。既然牺牲了效率,就必然带来安全性方面的完善。堆的建立与调整是一个反复循环的过程,但是堆也是一种数据结构,既然是数据结构,就拥有先天性的一些优势。这些优势,有些是看不见的,无法在程序代码中体现的,那会体现在哪里呢?可能会体现在程序编译运行时,体现在数据存储与读取时。数据在内存中的分配无非就是堆式栈式此类云云。一般的数组我们认为都是分配在栈上的,是一片连续的区域。但是,我想的问题是,这个规模的数组真的完全是连续分配在栈上的吗?别多想了,此事与本主题无关。

 

 

哎,说了这么多废话(有些话禁不起推敲,有些错误),无非就是想表明几点:

1. 不以时间复杂度论程序运行的快慢

2. 就效率而言,递归分治的思想必然优于迭代循环

3. 程序运行时发生的事情大多对我们来说都是透明的,大多情况下我们唯一能看到的就是结果

4. 排序算法的存在不仅仅是给数据排序,同时还在给程序员的能力进行排序

 


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值