歸並排序
歸並排序 (merge sort) 是一類與插入排序、交換排序、選擇排序不同的另一種排序方法。歸並的含義是將兩個或兩個以上的有序表合並成一個新的有序表。歸並排序有多路歸並排序、兩路歸並排序 , 可用於內排序,也可以用於外排序。這里僅對內排序的兩路歸並方法進行討論。
1.兩路歸並排序算法思路
①把 n 個記錄看成 n 個長度為1的有序子表;
②進行兩兩歸並使記錄關鍵字有序,得到 n/2 個長度為 2 的有序子表;
③重復第②步直到所有記錄歸並成一個長度為 n 的有序表為止。
【例】 有一組關鍵字 {4,7,5,3,2,8,6,1},n=8, 將其按由小到大的順序排序。 兩路歸並排序操作過程如圖 9.12 所示,其中i 為子表長度。
2.算法實現
此算法的實現不像圖示那樣簡單,現分三步來討論。首先從宏觀上分析,首先讓子表表長 L=1 進行處理;不斷地使 L=2*L ,進行子表處理,直到 L>=n 為止,把這一過程寫成一個主體框架函數 mergesort 。然后對於某確定的子表表長 L ,將 n 個記錄分成若干組子表,兩兩歸並,這里顯然要循環若干次,把這一步寫成一個函數 mergepass ,可由 mergesort 調用。最后再看每一組(一對)子表的歸並,其原理是相同的,只是子表表長不同,換句話說,是子表的首記錄號與尾記錄號不同,把這個歸並操作作為核心算法寫成函數 merge ,由 mergepass 來調用。假設我們有一個沒有排好序的序列,那么首先我們使用分割的辦法將這個序列分割成一個一個已經排好序的子序列,然后再利用歸並的方法將一個個的子序列合並成排序好的序列。分割和歸並的過程可以看下面的圖例。
3.算法主要思想
template<class T>
void merge( T r[],T r2[],int s,int mid,int t)
//s為第一個子表首元素的下標,mid為第一個子表末元素的下標
//t為第二個子表末元素的下標
{ int i,j,k;
i=s;j=mid+1;k=s; //k是r2的初始指針
while((i<=mid)&&(j<=t))
{ k=k+1;
if(r[i].key<=r[j].key){r2[k]=r[i];i++;}
else{r2[k]=r[j];j++;}
}
while(i<=mid){k++;r2[k]=r[i];i++;}
while(j<=t){k++;r2[k]=r[j];j++;}
} //merge
4. 算法的Java實現
Java實現的二路歸並排序的算法如下:
package algorithms; public class myMergeSort { static int number=0; public static void main(String[] args) { int[] a = {26, 5, 98, 108, 28, 99, 100, 56, 34, 1 }; printArray("排序前:",a); MergeSort(a); printArray("排序后:",a); } private static void printArray(String pre,int[] a) { System.out.print(pre+"\n"); for(int i=0;i<a.length;i++) System.out.print(a[i]+"\t"); System.out.println(); } private static void MergeSort(int[] a) { // TODO Auto-generated method stub System.out.println("開始排序"); Sort(a, 0, a.length - 1); } private static void Sort(int[] a, int left, int right) { if(left>=right) return; int mid = (left + right) / 2; //二路歸並排序里面有兩個Sort,多路歸並排序里面寫多個Sort就可以了 Sort(a, left, mid); Sort(a, mid + 1, right); merge(a, left, mid, right); } private static void merge(int[] a, int left, int mid, int right) { int[] tmp = new int[a.length]; int r1 = mid + 1; int tIndex = left; int cIndex=left; // 逐個歸並 while(left <=mid && r1 <= right) { if (a[left] <= a[r1]) tmp[tIndex++] = a[left++]; else tmp[tIndex++] = a[r1++]; } // 將左邊剩余的歸並 while (left <=mid) { tmp[tIndex++] = a[left++]; } // 將右邊剩余的歸並 while ( r1 <= right ) { tmp[tIndex++] = a[r1++]; } System.out.println("第"+(++number)+"趟排序:\t"); // TODO Auto-generated method stub //從臨時數組拷貝到原數組 while(cIndex<=right){ a[cIndex]=tmp[cIndex]; //輸出中間歸並排序結果 System.out.print(a[cIndex]+"\t"); cIndex++; } System.out.println(); } }
運行后的輸出結果如下所示:
排序前: 26 5 98 108 28 99 100 56 34 1 開始排序 第1趟排序: 5 26 第2趟排序: 5 26 98 第3趟排序: 28 108 第4趟排序: 5 26 28 98 108 第5趟排序: 99 100 第6趟排序: 56 99 100 第7趟排序: 1 34 第8趟排序: 1 34 56 99 100 第9趟排序: 1 5 26 28 34 56 98 99 100 108 排序后: 1 5 26 28 34 56 98 99 100 108
5.算法分析
(1)穩定性
歸並排序是一種穩定的排序。
(2)存儲結構要求
可用順序存儲結構。也易於在鏈表上實現。
(3)時間復雜度
對長度為n的文件,需進行 趟二路歸並,每趟歸並的時間為O(n),故其時間復雜度無論是在最好情況下還是在最壞情況下均是O(nlgn)。
(4)空間復雜度
需要一個輔助向量來暫存兩有序子文件歸並的結果,故其輔助空間復雜度為O(n),顯然它不是就地排序。
注意:
若用單鏈表做存儲結構,很容易給出就地的歸並排序。