申明:此博文轉自他人,感受不錯特轉載供你們分享算法
摘要:通常評判排序算法的標準有時間代價,空間代價和穩定性。本文主要討論性質相對比較好且做者喜歡的快速排序算法和歸併排序算法,並對此這作了必定比較。數組
正文:緩存
常見的排序算法大體分爲四類:
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