我最喜欢的排序算法——快速排序和归并排序

申明:此博文转自他人,感受不错特转载供你们分享算法

摘要通常评判排序算法的标准有时间代价,空间代价和稳定性。本文主要讨论性质相对比较好且做者喜欢的快速排序算法和归并排序算法,并对此这作了必定比较。数组

正文:缓存

常见的排序算法大体分为四类:
1.插入排序:直接插入排序,Shell排序
2.选择排序:直接选择排序,堆排序
3.交换排序:冒泡排序,快速排序
4.归并排序优化

而对排序算法的通常评判标准有:
              时间代价:比较次数、移动次数
              空间代价:额外空间、堆栈深度
              稳定性:存在多个具备相同排序码的记录排序后这些记录的相对次序保持不变ui

下面咱们先用这些评判标准对这些算法作一下基本评价:

spa

从这个表中能够看出,快速排序、归并排序和堆排序的时间代价是比较小的,而其余几个的时间代价相对比较大。咱们知道时间复杂度是评判一个算法的最主要标准。程序运行速度直接关系着算法的可行性。而真正美妙的算法也一定是运行速度比较快的。然而,因为如今计算机硬件的发展,尤为是多级缓存的引入,致使堆排序在实际运行中并不快。并且堆排序算法相对比较难理解,程序实现也相对困难,这样的算法显然不是美妙的算法。至少在快速排序面前很难找到优点。excel

而对于快速排序和归并排序,咱们先作一简单介绍,而后分别分析,最后对比分析。htm

快速排序:
算法思想:以第一个元素为准,小于该元素的放在左边,不小于该元素的放在右边,而后对两侧元素递归排序。
算法:
void quicksort(int l, int u)
{   int i, m;
    if (l >= u) return;
    m = l;
    for (i = l+1; i <= u; i++)
        if (x[i] < x[l])
            swap(++m, i);
    swap(l, m);
    quicksort(l, m-1);
    quicksort(m+1, u);
}
这里假设x为全局变量。blog

改进:快速排序有一个很大不足就是对于比较有序的数组排序效率很低,并且当数组较短时快速排序并非最快的。应对这些状况有三种简单经常使用的改进:
随机化改进:不是选取第一个值为基准,而是随机选取。
平衡化改进:取第一个、最后一个和中间点三个值中中间值为基准进行排序。
设置阀值--混合排序:当数组长度小于某一值时使用其余较快的排序。排序

算法分析:
时间代价:最好状况是O(n log n),最坏状况是O(n2)。若是设f(n)为数组长为n时的比较次数,则f(n)=[(f(1)+f(n-1))+(f(2)+f(n-2))+...+(f(n-1)+f(1))]/n.
利用数学知识易知f(n)=(n+1)*[1/2+1/3+...+1/(n+1)]-2n~1.386nlog(n).
空间代价:程序所需的空间即为堆栈深度(用于存储l,u,m),因此空间代价为O(log(n))
稳定性:快速排序时不稳定的,即不保序的。

评价:快速排序的时间代价比较低,空间代价也比较低,算是时空代价至关好的算法。并且在下面的数值试验中也会发现,快速排序效率仍是很好的。可是最大的不足使快速排序不稳定。好比在excel中进行排序,咱们天然但愿排序结果是稳定的(即相同的数排序后与原来的顺序相同)。

归并排序:
算法思想:将长为的n序列分为长度至关的左右两列,分别排序,而后再合并。即先分后合。
算法:
void merge_sort(int l,int u)
{
if(l+1>=u){basic_merge_sort(l,u);return;}
int c=(l+u)/2;
merge_sort(l,c);
merge_sort(++c,u);
merge(l,u);
}
其中basic_nerge_sort算法为:
void basic_merge_sort(int l,int u)
{
if((u-l==1)&&(x[l]>x[u]))
   swap(l,u);
}
其中的merge算法做用是:将两个有序的序列排成一个有序序列,算法以下:
void merge(int l,int u)
{
int c=(l+u)/2,j=c+1,i;
for(i=l;i<=u;i++)
   y[i]=x[i];
i=l;
while(l<=c&&j<=u)
{
   if(y[l]>y[j]) x[i++]=y[j++];
   else x[i++]=y[l++];
}
while(l<=c) x[i++]=y[l++];
while(j<=u) x[i++]=y[j++];
}
改进:归并排序使用时基本上使用的和这相似。
算法分析:
时间代价:设f(n)为数组长为n时的比较次数,则f(n)=f(n/2)+f((n+1)/2)+n.则利用数学知识很容易看出f(n)为O(nlog(n))的。
空间代价:归并排序所需空间除了堆栈深度之外还须要开长度为n的空间。因此归并排序的空间代价为O(n)。
稳定性:因为归并排序中并无使用出现对换,因此排序时稳定的。

评价:归并排序时间代价是比较理想的,并且算法是稳定的,这个是很好的。可是不足的是排序的空间代价比较大,须要开一个与原数组一样大小的数组。

二种算法对比:
时间代价
:从时间复杂度上看,两个算法势均力敌。但理论分析并不等于实际运行结果。因而我对两种算法用C实现了一下,分别用visual stdio C++6.0和Dev C++编译,在个人COMPAQ B1800笔记本(1.73GHz主频)上运行。运行结果以下:(N为数组长度,因为排序算法很快,且快排运行时间随机性比较大,我对每一个排序都运行了times次,每次数组元素都是随机选取)
visual stdio C++6.0上运行时间(ms)
N和times                   归并 快排
N=500 times=10000 1395 2593
N=1000 times=10000 3165 5645
N=2000 times=10000 6974 12115
N=10000 times=1000 4308 6986

Dev C++上最优化编译后运行时间(ms)
N和times                  归并 快排
N=500 times=10000 591 594
N=1000 times=10000 1515 907
N=2000 times=10000 2620 2381
N=10000 times=1000 3156 3172

两个编译器的运行时间很出乎意料,不光Dev C++上运行时间下降了,并且连二者的相对速度都不同。从VC上来看,显然归并要优于快排,并且又是很明显。而从Dev上来看,结果就不同了,二者通常状况下运行速度同样,部分状况下快排较好。这个运行结果与网上的一致评论比较类似。

对于这种状况个人解释:不一样编译器编译原理不一样,众所周知,Dev编译的结果通常是明显优于VC编译结果的,这里数据不一样的缘由部分也就是这个。而不一样编译器编译的执行文

件里都会有些辅助信息,这些必定程度上下降了程序的运行速度,这也是在VC上二者运行速度相差很大的缘由。再加上如今电脑各级内存的引入使得程序运行速度的快慢远远不能

只从理论分析值上来看。因此两个编译器的运行结果是大大不一样的。

不过整体来讲,两种排序的运行效率应该是相差无几的。不过若是选用VC编译器的话,归并有必定优点。但若是选用其余变异效果比较好的编译器,二者效率相差就不明显了。

空间代价:正如上面所分析的那样,快排的空间代价为堆栈深度,但快排最坏状况堆栈深度为n,最好状况为log(n),平均状况为O(log(n))。
归并排序堆栈深度为O(log(n)),但还须要额外的大小为n的空间,因此空间代价为O(n)。
从空间代价上来看,归并排序不如快速排序。

稳定性:从上面的分析上知道,快速排序时不稳定的,而归并排序是稳定的。在这方面两个排序彻底不一样。若是对稳定性没有要求,则二者没有太大差距;但若是对稳定性有要求

,则快速排序则不适用。因此归并排序在这方面有一个比较大的优点。

从上面三个方面上看,快速排序的时空代价相对较小,略比归并要好。这应该是你们特别看好快速排序的缘由。甚至快排仍是20世纪十大经典算法之一。但归并排序的劣势并非很明显,并且归并排序的算法思想是如此简单。更重要的是,归并排序是稳定的。这些应该是归并排序能与快速排序抗衡的主要缘由。

这两个排序算法是我最喜欢的。固然若是非要从二者之间选一个最最喜欢的话,我会选择归并排序。一方面在我彻底不知道归并排序的状况下,本身独立写出了它的算法并上机实

现了。另外一方面,归并排序思想简单,是稳定的,适用性优于快速排序。因为其稳定性,我能够大胆的copy这些代码到我须要用它的地方。

参考文献:
1.Jon Bentley The Most Beautiful Code I Never Wrote
2.http://baike.baidu.com/view/297739.htm
3.http://baike.baidu.com/view/115472.htm

相关文章
相关标签/搜索