歸併排序(Merge Sort)
(1)演算法思想
歸併排序採用了分治策略(divide-and-conquer),就是將原問題分解為一些規模較小的相似子問題,然後遞迴解決這些子問題,最後合并其結果作為原問題的解。
歸併排序將待排序數組A[1..n]分成兩個各含n/2個元素的子序列,然後對這個兩個子序列進行遞迴排序,最後將這兩個已排序的子序列進行合并,即得到最終排好序的序列。具體排序過程如下圖所示:
歸併排序中一個很重要的部分是兩個已排序序列合并的過程,這裡需要另外開闢一塊新的空間來作為儲存這兩個已排序序列的臨時容器。假設對A[p..r]序列進行合并,已知A[p..q]及A[q+1..r]為已排序的序列,合并的具體步驟為:
Step 1:建立兩個數組L、R分別儲存待定序序列A[p..q]和A[q+1..r],將待排序序列中的對應元素copy到L和R中,L和R最後設定一個極大值作為“哨兵”;
Step 2:令指標i指向L的起始元素,j指向R的起始元素,k指向A待合并部分的起始元素A[p];
Step 3:若L[i]≤R[j],令A[k]=L[i],i=i+1,k=k+1;
否則,令A[k]=R[j],j=j+1,k=k+1;
(這一步即依次比較i、j所指向的元素,將較小值依次放入到A中相應位置。)
Step 4 :重複Step 3,r-p+1次後停止,即依次確定A[p..q]每個位置上的元素。
經過合併作業後,A[p..q]為一個有序序列。若待定序序列為(38, 49, 65, 97, 13, 27, 49, 76),p=1,q=4,, r=8,即A[1..4]和A[5..8]分別為有序序列,則合併作業的具體過程如下圖所示:
(2)虛擬碼
MERGE SORT(A, p, r) //對A[p..r]進行歸併排序
1 if p < r
2 then q ← ⎣(p+r)/2⎦ //將A[p..r]分成兩個子序列進行遞迴歸併排序
3 MERGE-SORT (A, p, q)
4 MERGE-SORT (A, q+1, r)
5 MERGE (A, p, q, r) //將已排序的兩個子序列進行合并
MERGE(A, p, q, r)
1 n1 ← q-p+1; //計算左半部分已排序序列的長度
2 n2 2 ← r-q; //計算右半部分已排序序列的長度
3 create arrays L[1..n1+1] and R[1..n2+1] //建立兩個數組臨時儲存兩個已排序序列,長度+1是因為最後有一個標誌位
4 for i ← 1 to n1
5 do L[i] ← A[p + i-1] //copy左半部分已排序序列到L中
6 for j ← 1 to n2
7 do R[j] ← A[q + j] //copy右半部分已排序序列到R中
8 L[n1+1] ← ∞ //L、R最後一位設定一個極大值作為標誌位
9 R[n2+1] ← ∞
10 i ← 1
11 j ← 1
12 for k ← p to r //進行合并
13 do if L[i] < R[j]
14 then A[k] ← L[i]
15 i ← i + 1
16 else A[k] ← R[j]
17 j ← j + 1
(3)代碼實現
注意,開始排序時,p、r表示的是待排序數組的起始下標和終止下標,例如,若A=(38, 49, 65, 97, 13, 27, 49, 76),對A進行排序,調用MergeSort(A, 0, 7),即p=0, r=7.
void Merge(int A[],int p,int q,int r){ int i,j,k; int n1=q-p+1; int n2=r-q; int *L=new int[n1+1]; //開闢臨時儲存空間 int *R=new int[n2+1]; for(i=0;i<n1;i++) L[i]=A[i+p]; //數組下標從0開始時,這裡為i+p for(j=0;j<n2;j++) R[j]=A[j+q+1]; //數組下標從0開始時,這裡為就j+q+1 L[n1]=INT_MAX; //"哨兵"設定為整數的最大值,INT_MAX包含在limits.h標頭檔中 R[n2]=INT_MAX; i=0; j=0; for(k=p;k<=r;k++) //開始合并 { if(L[i]<=R[j]) A[k]=L[i++]; else A[k]=R[j++]; }}void MergeSort(int A[],int p,int r) { if(p<r) { int q=(p+r)/2; MergeSort(A,p,q); MergeSort(A,q+1,r); Merge(A,p,q,r); }
通過簡單的變化,L、R也可以不使用“哨兵”,當L中的元素全部填入A中時,後面過程直接將R剩餘部分copy到A中;當R中的元素全部填入A中時,後面過程直接將L剩餘部分copy到A中。具體實現如下:
void Merge2(int A[],int p,int q,int r){ int i,j,k; int n1=q-p+1; int n2=r-q; int *L=new int[n1]; int *R=new int[n2]; for(i=0;i<n1;i++) L[i]=A[i+p]; for(j=0;j<n2;j++) R[j]=A[j+q+1]; i=0; j=0; for(k=p;k<=r;k++) { if(j==n2||L[i]<=R[j]) //注意:此處加了j==n2的或條件 A[k]=L[i++]; else A[k]=R[j++]; }}
(4)演算法分析
a.歸併排序的時間複雜度為:Θ(nlgn),其中 MERGE(A, p, q, r)的時間複雜度為Θ(n)。
b.歸併排序並不是一種原地排序,因為需要額外申請空間來充當臨時容器。
c.歸併排序是一種穩定排序。