淺入淺出資料結構(21) 合併排序

2022-04-28 15:30:11 字數 2601 閱讀 6734

在講解合併排序之前,我們先來想一想下面這個問題如何解決:

有兩個陣列a和b,它們都已各自按照從小到大的順序排好了資料,現在我們要把它們合併為乙個陣列c,且要求c也是按從小到大的順序排好,請問該怎麼做?

這個問題非常容易解決,我們將a、b和c都視為佇列,然後不斷比較a和b的首部,取出其中更小的資料出隊,然後將該資料插入佇列c,若a或b有一方為空了,則將另一方資料順序出隊再插入c即可:

int i=0,j=0,z=0

;while(ib_size)

while(ic[z++]=a[i++];

while(jc[z++]=b[j++];

顯然,上述問題的解決非常簡單,時間複雜度為o(n),n為總資料量。也就是說,如果我們有兩個已排好序的陣列,那麼我們就可以快速地將它們合併起來,而這,就是合併排序的根本思想。

回顧快速排序,可以看出快速排序的做法其實就是:選取樞紐,將小於樞紐的元素組成乙個子陣列,大於樞紐的元素組成乙個子陣列,只要這兩個子陣列排好序,整個陣列就能排好序,至於兩個子陣列怎麼排好序,遞迴實現。

合併排序的做法與快速排序有些類似,只是「過程」反了過來:將原陣列對半分為兩個子陣列,只要這兩個子陣列排好序,我就能將它們通過合併(上述問題的解法)來得到有序的原陣列,至於怎麼得到兩個排好序的子陣列,遞迴實現。

用偽**來表示合併排序的過程,就是這樣:

void mergesort(int *src,unsigned int left,unsigned int

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)

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)

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)

至此,合併排序就實現完畢了,其占用的空間顯然比快速排序等演算法要多出一倍,那麼其時間複雜度如何呢?我們就來簡單的算算看。

首先,我們假設進行合併排序的陣列大小為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拷貝資料回原陣列也是一項花費巨大的工作。

淺入淺出資料結構(18) 希爾排序

而希爾排序就是 簡單地 將這個道理應用到了插入排序中,將插入排序小小的公升級了一下。那麼,希爾排序是怎麼將這個道理應用於插入排序的呢?我們先來回顧一下插入排序的 void insertionsort int a,unsigned int size 不難看出,在插入排序中,對於每乙個元素,我們都令其執...

淺入淺出資料結構(16) 插入排序

從這一篇博文開始,我們將開始討論排序演算法。所謂排序演算法,就是將給定資料根據關鍵字進行排序,最終實現資料依照關鍵字從小到大或從大到小的順序儲存。而這篇博文,就是要介紹一種簡單的排序演算法 插入排序 insertion sort 為了使精力專注於排序演算法本身,而不是對資料的分析 處理,若無特殊說明...

淺入淺出資料結構(17) 有關排序演算法的分析

這一篇博文我們將討論一些與排序演算法有關的定理,這些定理將解釋插入排序博文中提出的疑問 為什麼氣泡排序與插入排序總是執行同樣數量的交換操作,而選擇排序不一定?同時為講述高階排序演算法做鋪墊 高階排序為什麼會更快。在討論相關定理之前,我們必須先掌握乙個與順序有關的概念 逆序數。所謂逆序數,就是 逆序組...