合併排序的關鍵步驟在於合併步驟中的合併兩個已排序子序列。爲做合併,引入一個輔助過程MERGE(A, p, q, r), 其中A是一個數組,p、q和r是下標,滿足p小於等於q小於r。該過程假設子數組A[p...q] 和A[q+1...r]都已排好序,並將它們合併成一個已排好序的子數組代替當前子數組A[p.. r] 。
下面來說明該算法的工作過程:
舉撲克牌這個例子,假設有兩堆牌面朝上地放在桌上,每一堆都是已排序的,最小的牌在最上面。我們希望把這兩堆牌合併成一個排好序的輸出堆,面朝下地放在桌上。基本步驟包括在面朝上的兩堆牌中,選取頂上兩張中較小的一張,將其取出後(它所在堆的頂端又會露出一張新的牌)面朝下地放到輸出堆中。重複這個步驟,直到某一輸入堆爲空時爲止。這時,把輸入堆中餘下的牌面朝下地放入輸出堆中即可。從計算的角度來看,每一個基本步驟所花時間是個常量,因爲我們只是查香並比較頂上的兩張牌。又因爲至多進行n次比較,所以合併排序的時間爲。
在僞代碼實現時,我們增加一張“哨兵牌”。在每一堆的底部放上一張“哨兵牌" (sentinel card) , 它包含了一個特殊的值,用於簡化代碼。此處,利用來作爲哨兵值,這樣每當露出一張值爲的牌時,它不可能是兩張中較小的牌,除非另一堆也露出了哨兵牌。但是,一且發生這種兩張哨兵牌同時出現的情況時,說明兩堆牌中的所有非哨兵牌都已經被放到輸出堆中去了。因爲我們預先知道只有r-p+1張牌會被放到輸出堆中去,因此, 一旦執行了r-p+1個基本步驟後(兩堆牌合併過程中的運行次數),算法就可以停止下來了。
僞代碼:
MERGE(A,p,q,r)
n1 <- q-p+1
n2 <- r-q
create arrays L[1...n1+1] and R[1...n2+1]
for i<-1 to n1
do L[i] <- A[p+i-1]
for j<-1 to n2
do R[j] <- A[q+j]
L[n1+1] <- 極大值哨兵元素
R[n2+1] <- 極大值哨兵元素
i<-1
j<-1
for k<- p to r
do if L[i] <= R[j]
then A[k] <- L[i]
i <- i+1
else A[k] <- R[j]
j <- j+1
MERGE-SORT(A,p,r)
if p<r
then q<-(p+r)/2
MERGE-SORT(A,p,q)
MERGE-SORT(A,q+1,r)
MERGE(A,p,q,r)
C/C++代碼:
#include <stdio.h>
#include <string.h>
#include <limits.h>
using namespace std;
void Merge(int *A, int p, int q, int r) {
int n1 = q - p + 1, n2 = r - q;
int *L = new int[n1 + 1];
int *R = new int[n2 + 1];
//分成兩部分的子數組分別存在L和R中
for (int i = 0; i < n1; i++)
L[i] = A[p + i];
for (int j = 0; j < n2; j++)
R[j] = A[q + 1 + j];
L[n1] = R[n2] = INT_MAX;
//L和R的哨兵元素
int i = 0, j = 0;
//當L和R均未遍歷到哨兵元素時,哪個小哪個就先放到數組A中相應位置
//當其中有一個遍歷到哨兵元素時,由於哨兵元素是極大值,故if選擇時就會將另一個子數組剩餘元素放到數組A中剩餘位置中
for (int k = p; k <= r; k++) {
if (L[i] <= R[j]) {
A[k] = L[i];
i = i + 1;
} else {
A[k] = R[j];
j = j + 1;
}
}
}
void MergeSort(int A[], int p, int r) {
if (p < r) {
int q = (p + r) / 2;
//分解,遞歸地調用MergeSort函數
// 繼續分解直到子數組足夠小時(即p和q相差1時,此時再調用MergeSort函數已經無法再拆分成更小子問題)開始合併解決子問題
MergeSort(A, p, q);
MergeSort(A, q + 1, r);
//合併子問題的解
Merge(A, p, q, r);
}
}
int main() {
int n;
scanf("%d", &n);
int *A = new int[n];
for (int i = 0; i < n; i++)
scanf("%d", &A[i]);
printf("input complete\n");
MergeSort(A, 0, n - 1);
printf("print the sorted number:\n");
for (int i = 0; i < n; i++)
printf("%d ", A[i]);
return 0;
}
運行結果如下:
8
8 7 6 5 4 3 2 1
input complete
print the sorted number
1 2 3 4 5 6 7 8
Process finished with exit code 0
Python3代碼:
def merge(a, p, q, r):
L, R = [], []
for k, element in enumerate(a):
if p <= k <= q:
L.append(element)
elif q + 1 <= k <= r:
R.append(element)
# 分成兩部分的子數組分別存在L和R中
L.append(float('inf'))
R.append(float('inf'))
# 給L和R兩個列表末尾各添加一個無窮大值作爲哨兵
i, j = 0, 0
# 當L和R均未遍歷到哨兵元素時,哪個小哪個就先放到數組A中相應位置
# 當其中有一個遍歷到哨兵元素時,由於哨兵元素時極大值,故if選擇時就會將另一個子數組剩餘元素放到數組A中剩餘位置中
for k, element in enumerate(a, p):
if k <= r:
if L[i] <= R[j]:
a[k] = L[i]
i = i + 1
else:
a[k] = R[j]
j = j + 1
def merge_sort(a, p, r):
if p < r:
q = int((p + r) / 2)
# 分解,遞歸地調用MergeSort函數
# 繼續分解直到子數組足夠小時(即p和q相差1時,此時再調用MergeSort函數已經無法再拆分成更小子問題)開始合併解決子問題
merge_sort(a, p, q)
merge_sort(a, q + 1, r)
# 合併子問題的解
merge(a, p, q, r)
A = []
while True:
try:
A.append(int(input()))
except:
print('input complete')
break
merge_sort(A, 0, len(A) - 1)
print("print the sorted number:")
for index, item in enumerate(A):
print(item, end=' ')
# python3默認打印會換行,加上end=' ',則每次後面會自動加上' '中內容而不是換行
運行結果如下:
8
7
6
5
4
3
2
1
input complete
print the sorted number:
1 2 3 4 5 6 7 8
Process finished with exit code 0