1. 程式人生 > >演算法實現:歸併(合併)排序(C/C++、Python)

演算法實現:歸併(合併)排序(C/C++、Python)

合併排序的關鍵步驟在於合併步驟中的合併兩個已排序子序列。為做合併,引入一個輔助過程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