歸併排序的基礎概念就不講了,我的博客只會寫有創造性的東西。
歸併排序的代碼如下:
void mergesort(int* arr,int n){
if(n>1){
mergesort(arr,n/2);
mergesort(arr+n/2,n-n/2);
merge(buff,arr,n/2,arr+n/2,n-n/2);
memcpy(arr,buff,n);
}
}
迭代版
標準的歸併排序是個遞歸過程,不過要用迭代過程表達也很簡單,就是先兩個兩個一排,再四個四個一排。。。。
如:
98 15 73 20 76 27 34 82
15 98 20 73 27 76 34 82 //第一趟
15 20 73 98 27 34 76 82 //第二趟
15 20 27 34 73 76 82 98 //第三趟
代碼不寫了,代碼比遞歸版的複雜一點,效率也提高了一點
原地歸併算法
標準歸併排序使用的歸併算法要額外申請一個大小爲n的數組暫時存放歸併數據,原地歸併算法就不用,不過要以時間爲代價,它的一次歸併過程的時間複雜度是O(n^2)。
舉個例子:
15 20 76 98 27 34 73 82
這裏有兩段有序數組,檢測到應該把27、34、73插入到20和76之間,也就是把
76 98 27 34 73 這段數組循環左移兩位。
如何把數組循環左移k位呢?
用的是翻大餅算法,先把前k個元素翻轉過來,再把後n-k個元素翻轉過來,在翻轉這n個元素。
過程如下:
15 20 76 98 27 34 73 82 //檢測到應該把27-73插入到20和76之間
15 20 98 76 27 34 73 82 //翻轉98、76這段數組
15 20 98 76 73 34 27 82 //翻轉27、34、73這段數組
15 20 27 34 73 76 98 82 //翻轉這5個元素
15 20 27 34 73 76 98 82 //前面的數組已經有序,繼續歸併後面的數組
假設待排序元素是均勻分佈的,那麼待歸併的前後兩段有序數組元素是交叉排列,一次翻大餅算法只能插入O(1)個元素,因此原地歸併算法的時間複雜度是T(n)=T(n-1)+n,
得到T(n)=O(n^2)。
那麼使用了原地歸併算法的歸併排序的時間複雜度就是T(n)=2T(n/2)+n^2,得到T(n)=n^2。
原地歸併排序在效率上肯定是不如插入排序的,具體有什麼優勢暫時沒想到。
交替歸併排序
從標準歸併排序的代碼中可以看到,每次利用緩衝區完成歸併算法後,又要把緩衝區的內容複製回原數組裏面去,這浪費了一些時間。
利用緩衝區完成歸併算法後,完全可以把緩衝區和原數組的身份對調,就省去了複製回去的時間。
98 15 73 20 76 27 34 82 //在A數組
15 98 20 73 27 76 34 82 //在B數組
15 20 73 98 27 34 76 82 //在A數組
15 20 27 34 73 76 82 98 //在B數組
這裏有個問題,最後的有序序列在A數組還是在B數組,寫代碼的時候注意一下就可以了。
鏈表歸併排序
鏈表歸併排序很容易理解了,就是把前後兩半先排完,再用鏈表歸併算法。
鏈表歸併排序不存在數組歸併排序需要緩衝區的問題,好處還是很大的。
表歸併排序
這個肯定又是我的重新發明了
不知道大家有沒有看過嚴蔚敏寫的《數據結構(C語言版)》裏面的表插入排序呢?
同樣的,歸併排序也可以完美地加上一個“表”字。
剛纔說過,數組歸併排序需要額外申請一個緩衝區,鏈表歸併排序卻不存在這個問題。
在表歸併排序裏,申請緩衝區是必須的,但存的不是元素的副本,而是元素的下標,兩個數組形成一個靜態鏈表。
直接舉例子:
一開始數組無序,初始化下標數組,全都初始爲-1
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
98 |
15 |
73 |
20 |
76 |
27 |
34 |
82 |
-1 |
-1 |
-1 |
-1 |
-1 |
-1 |
-1 |
-1 |
第一趟歸併,變成兩個一組,15->98,20->73,27->76,34->82
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
98 |
15 |
73 |
20 |
76 |
27 |
34 |
82 |
-1 |
0 |
-1 |
2 |
-1 |
4 |
7 |
-1 |
第二趟歸併,變成四個一組,15->20->73->98,27->34->76->82
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
98 |
15 |
73 |
20 |
76 |
27 |
34 |
82 |
-1 |
3 |
0 |
2 |
7 |
6 |
4 |
-1 |
第三趟,成爲有序的靜態鏈表
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
98 |
15 |
73 |
20 |
76 |
27 |
34 |
82 |
-1 |
3 |
4 |
5 |
7 |
6 |
2 |
0 |
這裏又有個問題,需要記住每個鏈表的首元素的下標,這個很容易解決,這裏不討論了。
現在,鏈表已經有序了,還要把元素歸位,要用的是嚴蔚敏寫的《數據結構(C語言版)》重排記錄的算法。
描述有點複雜,這裏不寫了,想了解自己看書去吧。
這個表歸併排序算法,時間複雜度還是O(nlogn),空間複雜度還是O(n)。
不過還是有一點優勢的,特別是對於元素塊頭比較大的數組。
時間上說,在排序時元素不需要移動,只有在歸位時,需要交換n-1次。
空間上說,申請的輔助空間只要存整型變量,不用存整個結構體。
也有點劣勢,它畢竟是鏈表,不符合空間局部性原理,cache對它就沒用了。
多路歸併
歸併多個有序數組,可以用堆,可以用敗者樹,也可以用哈夫曼樹,不寫了。