在講解合併排序以前,咱們先來想想下面這個問題如何解決:算法
有兩個數組A和B,它們都已各自按照從小到大的順序排好了數據,如今咱們要把它們合併爲一個數組C,且要求C也是按從小到大的順序排好,請問該怎麼作?數組
這個問題很是容易解決,咱們將A、B和C都視爲隊列,而後不斷比較A和B的首部,取出其中更小的數據出隊,而後將該數據插入隊列C,若A或B有一方爲空了,則將另外一方數據順序出隊再插入C便可:spa
int i=0,j=0,z=0; while(i<a_size && j<b_size) { if(a[i]<b[j]) c[z++]=a[i++]; else c[z++]=b[j++]; } while(i<a_size) c[z++]=a[i++]; while(j<b_size) c[z++]=b[j++];
顯然,上述問題的解決很是簡單,時間複雜度爲O(N),N爲總數據量。也就是說,若是咱們有兩個已排好序的數組,那麼咱們就能夠快速地將它們合併起來,而這,就是合併排序的根本思想。code
回顧快速排序,能夠看出快速排序的作法其實就是:選取樞紐,將小於樞紐的元素組成一個子數組,大於樞紐的元素組成一個子數組,只要這兩個子數組排好序,整個數組就能排好序,至於兩個子數組怎麼排好序,遞歸實現。blog
合併排序的作法與快速排序有些相似,只是「過程」反了過來:將原數組對半分爲兩個子數組,只要這兩個子數組排好序,我就能將它們經過合併(上述問題的解法)來獲得有序的原數組,至於怎麼獲得兩個排好序的子數組,遞歸實現。排序
用僞代碼來表示合併排序的過程,就是這樣:遞歸
void MergeSort(int *src,unsigned int left,unsigned int right) {
if(left<right)
{ /*將原數組一分爲二*/ unsigned int center= (left+right)/2; /*經過遞歸實現子數組的排序*/ MergeSort(src,0,center); MergeSort(src,center+1,right);
/*僞代碼:將子數組合並*/
} }
有了僞代碼後,剩下的工做就是將僞代碼中的「空」填上去。接口
上述僞代碼有兩個「空」,一個是實現遞歸的基準情形,這個「空」很好填,由於根本不用填,爲何呢?由於只要數組大小大於1,咱們就一直劃分下去,那麼最終劃分獲得的子數組將會只有1個數據,此時這個子數組必爲「有序」,也就是說遞歸的基準情形必然存在。隊列
另外一個「空」則是實現子數組的合併,這個「空」咱們能夠參考本文一開始提出的問題的解法,可是該解法須要用到一個額外的數組C,且最終的有序數據都放進了C裏面,該怎麼辦呢?思路很簡單也很直接,那就是:既然你要一個額外數組,那我就給你一個額外數組tempArr,有序數據在tempArr裏面,而我但願它們在原數組裏面,那我就將tempArr裏的數據複製回來:內存
/*將子數組合併到tempArr*/ unsigned int i=left,j=center+1,z=left; //注意,i,j,z的初始化和範圍都要有所變化 while(i<=center&&j<=right) { if(src[i]<src[j]) tempArr[z++]=src[i++]; else tempArr[z++]=src[j++]; } while(i<=center) tempArr[z++]=src[i++]; while(j<=right) tempArr[z++]=src[j++]; /*將tempArr的數據拷貝到原數組中*/ for (z = left;z <= right;++z) src[z] = tempArr[z];
將上面的代碼填入到僞代碼中,並將僞代碼的參數稍加修改,便有了以下合併排序:
void MSort(int *src, int *tempArr, unsigned int left, unsigned int right) { if (left < right) { /*將原數組一分爲二*/ unsigned int center = (left + right) / 2; /*遞歸實現子數組排序*/ MSort(src, tempArr, left, center); MSort(src, tempArr, center + 1, right); /*將子數組合併到tempArr*/ unsigned int i=left,j=center+1,z=left; while(i<=center&&j<=right) { if(src[i]<src[j]) tempArr[z++]=src[i++]; else tempArr[z++]=src[j++]; } while(i<=center) tempArr[z++]=src[i++]; while(j<=right) tempArr[z++]=src[j++]; /*將tempArr的數據拷貝到原數組中*/ for (int i = left;i <= right;++i) src[i] = tempArr[i]; } }
爲了方便調用,咱們再實現一個小接口:
void MergeSort(int *src, unsigned int size) { int *tempArr = (int *)malloc(sizeof(int)*size); MSort(src, tempArr, 0, size - 1); free(tempArr); }
至此,合併排序就實現完畢了,其佔用的空間顯然比快速排序等算法要多出一倍,那麼其時間複雜度如何呢?咱們就來簡單的算算看。
首先,咱們假設進行合併排序的數組大小爲N且爲2的冪,T(N)表示對其排序耗費的時間,那麼就有:
1.T(1)=1
2.T(N)=2*T(N/2)+2N(兩個N分別爲合併耗費時間和拷貝回原數組所耗費的時間)
將2式左右除以N,得:
T(N)/N=T(N/2)/(N/2)+2
遞推該式,得:
T(N/2)/(N/2)=T(N/4)/(N/4)+2
T(N/4)/(N/4)=T(N/8)/(N/8)+2
……
T(2)/2=T(1)/1+2
將上述全部式子左側相加且右側相加,得:
T(N)/N+T(N/2)/(N/2)+T(N/4)/(N/4)+……+T(2)/2=T(N/2)/(N/2)+T(N/4)/(N/4)+……+T(1)/1+2*logN
化簡,得:
T(N)/N=T(1)/1+2*logN,即T(N)=N+2*N*logN=O(N*logN)
這個時間複雜度與快速排序的平均時間複雜度相同,比快速排序的最壞狀況要好得多,可是在實際應用中快速排序要比合並排序優先考慮,緣由在於合併排序須要更多的內存空間,而且從tempArr拷貝數據回原數組也是一項花費巨大的工做。