引言:衆所周知,歸併排序(合併排序)算法是基於分治策略的一個排序算法,其基本思想是:將待排序元素分成大小大致相同的2個子集合,分別對2個子集合進行排序,最終將排序好的子集合合併成爲所要求的排好序的集合。具體過程如下圖所示(動圖源於網絡):
如圖所示:歸併排序需要三個索引分別是i
,j
,k
。其中i
指向帶排序第一個子集合待比較元素,j
指向待排序另一個子集合待比較元素,k
指向數組b中的元素。
遞歸代碼如下:
public static void mergeSort(int a[],int l,int r){
if(l>=r) return; //遞歸尾->至少有兩個元素
int m = (l+r)/2;
mergeSort(a,l,m); //類似細胞分解,左半
mergeSort(a,m+1,r); //類似細胞分解,有半
merge(a,b,l,m,r); //將a數組排好序合併到b數組裏,最後複製回a
}
public static void merge(int a[],int b[],int l,int m,int r){
//1.開始歸併
int i=l,k=l,j=m+1;
while(i<=m&&j<=r){
if(a[i]<=a[j]){ //這是歸併排序是穩定性排序的關鍵條件,若取嚴格不等號,則不穩定
b[k++] = a[i++];
}else b[k++] = a[j++];
}
//2.對結尾元素進行處理
if(i<=m){
for(int q=i;q<=m;q++)
b[k++] = a[q];
}else{
for(int q=j;q<=r;q++)
b[k++] = a[q];
}
//3.複製回數組a
for(int q=l;q<=r;q++)
a[q] = b[q];
}
運行結果如下:
初始數組:49 38 65 97 76 13 27
排序數組:13 27 38 49 65 76 97
讀者可以發現在mergeSort遞歸算法裏面,每一次都要進入merge方法進行合併,可事實並不是每一次都需要。比如歸併的兩部分數組拼接到一起就已經是成序的數組或者輸入的數組就已經是有序的數組,這樣就不需要再進一步合併。因此可以在此前加一個判斷即可減少一定的歸併次數。
優化代碼如下:
public static void mergeSort(int a[],int l,int r){
if(l>=r) return; //遞歸尾->至少有兩個元素
int m = (l+r)/2;
mergeSort(a,l,m); //類似細胞分解,左半
mergeSort(a,m+1,r); //類似細胞分解,有半
if(a[m]>a[m+1])
merge(a,b,l,m,r); //將a數組排好序合併到b數組裏
}
以上通過判斷第一部分數組的最後一個元素是否小於另一部分數組第一個元素來實現,即加上if(a[m]>a[m+1])
判斷。因爲兩部分數組已經各自成序,只要判斷其邊緣元素大小即可。
補充:以上只討論了歸併排序的遞歸實現,是自頂向下的,實際上還有迭代法的實現,即自底向上。迭代法程序比遞歸法略長,但特點是可無需向數組中通過索引讀取元素,這使得迭代法可用在數據結構是鏈表的情況下進行排序。