9.8.3 歸併排序複雜度分析
我們來分析一下歸併排序的時間複雜度,一趟歸併需要將SR[1]~SR[n]中相鄰的長度為h的有序序列進行兩兩歸併。並將結果放到TR1[1]~TR1[n]中,這需要將待排序序列中的所有記錄掃描一遍,因此耗費O(n)時間,而由完全二叉樹的深度可知,整個歸併排序需要進行⌈log2n⌉趟,因此,總的時間複雜度為O(nlogn),而且這是歸併排序演算法中最好、最壞、平均的時間效能。
由于歸並排序在歸併過程中需要與原始記錄序列同樣數量的儲存空間存放歸併結果以及遞迴時深度為log2n的棧空間,因此空間複雜度為O(n+logn)。
另外,對代碼進行仔細研究,發現Merge函數中有if (SR[i]<SR[j])語句,這就說明它需要兩兩比較,不存在跳躍,因此歸併排序是一種穩定的排序演算法。
也就是說,歸併排序是一種比較佔用記憶體,但卻效率高且穩定的演算法。
9.8.4 非遞迴實現歸併排序
我們常說,“沒有最好,只有更好。”歸併排序大量引用了遞迴,儘管在代碼上比較清晰,容易理解,但這會造成時間和空間上的效能損耗。我們排序追求的就是效率,有沒有可能將遞迴轉化成迭代呢。結論當然是可以的,而且改動之後,效能上進一步提高了。來看代碼。
/* 對順序表L作歸併非遞迴排序 */void MergeSort2(SqList *L){ int* TR=(int*)malloc(L->length * sizeof(int)); /* 申請額外空間 */ int k=1; while(k<L->length) { MergePass(L->r,TR,k,L->length); k=2*k; /* 子序列長度加倍 */ MergePass(TR,L->r,k,L->length); k=2*k; /* 子序列長度加倍 */ }}
1) 程式開始執行,數組L為{50,10,90,30,70,40,80,60,20},L.length=9。
2) 第3行,我們事先申請了額外的數組記憶體空間,用來存放歸併結果。
3) 第5~11行,是一個while迴圈,目的就不斷的歸併有序序列。注意k值的變化,第8行與第10行,在不斷迴圈中,它將由1→2→4→8→16,跳出迴圈。
4) 第7行,此時k=1,MergePass函數將原來的無序數組兩兩歸併入TR,此函數代碼稍後再講。如圖9-8-11。
5) 第8行,k=2
6) 第9行,MergePass函數將TR中已經兩兩歸併的有序序列再次歸併回數組L.r中,如圖9-8-12。
7) 第10行,k=4,因為k<9,所以繼續迴圈,再次歸併,最終執行完第7~10行,k=16,結束迴圈,完成排序工作。如圖9-8-13。
從代碼中,我們能夠感受到,非遞迴的迭代做法更加直截了當,從最小的序列開始歸併直至完成。不需要像歸併的遞迴演算法一樣,需要先拆分遞迴,再歸併退出遞迴。
現在我們來看MergePass代碼是如何?的。
/* 將SR[]中相鄰長度為s的子序列兩兩歸併到TR[] */void MergePass(int SR[],int TR[],int s,int n){ int i=1; int j; while(i <= n-2*s+1) { Merge(SR,TR,i,i+s-1,i+2*s-1); /* 兩兩歸併 */ i=i+2*s; } if(i<n-s+1) /* 歸併最後兩個序列 */ Merge(SR,TR,i,i+s-1,n); else /* 若最後只剩下單個子序列 */ for(j =i;j <= n;j++) TR[j] = SR[j];}
1) 程式執行。我們第一次調用“MergePass(L.r,TR,k,L.length);”,此時L.r是初始無序狀態,TR為新申請的空數組,k=1,L.length=9。
2) 第5~9行,迴圈的目的就兩兩歸併,因s=1,n-2*s+1=8,為什麼迴圈i從1到8,而不是9呢。就是因為兩兩歸併,最終9條記錄定會剩下來,無法歸併。
3) 第7行,Merge函數我們前面已經詳細講過,此時i=1,i+s-1=1,i+2*s-1=2。也就是說,我們將SR(即L.r)中的第一個和第二個記錄歸併到TR中。然後第8行,i=i+2*s=3,再迴圈,我們就是將第三個和第四個記錄歸併到TR中,一直到第七和第八個記錄完成歸併。如圖9-8-14。
4) 第10~14行,主要是處理最後的尾數的,第11行是說將最後剩下的多個記錄歸併到TR中。不過由於i=9,n-s+1=9,因此執行第13~14行,將20放入到TR數組的最後。
5) 再次調用MergePass時,s=2,第5~9行的迴圈,由第8行的i=i+2*s可知,此時i就是以4為增量進行迴圈了。也就是說,是將兩個有兩個記錄的有序序列進行歸併為四個記錄的有序序列。最終再將最後剩下的第九條記錄“20”插入TR。
6) 後面的類似,略。
非遞迴的迭代方法,避免了遞迴時深度為log 2n的棧空間,空間只是用到申請歸併臨時用的TR數組,因此空間複雜度為O(n)。並且避免遞迴也在時間效能上有一定的提升,應該說,使用歸併排序時,盡量考慮用非遞迴方法。 出處:http://www.cnblogs.com/cj723/archive/2011/04/26/2026886.html