演算法實現:歸併(合併)排序(C/C++、Python)
阿新 • • 發佈:2018-11-10
合併排序的關鍵步驟在於合併步驟中的合併兩個已排序子序列。為做合併,引入一個輔助過程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