歸並排序(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.歸並排序是一種穩定排序。