算法學習與代碼實現3——合併排序
算法思路
合併排序使用的是分治法,分治法在《算法導論》上的介紹分爲三個步驟:
- 分解(Divide):將原問題分解成一些列子問題;
- 解決(Conquer):遞歸地解各個子問題。若問題足夠小,則直接求解。
- 合併(Combine):將子問題的結果合併成原問題的解。
同時,書上也介紹了合併排序(merge sort)相對應的三個步驟:
- 分解:將n個元素分成各含n/2個元素的子序列;
- 解決:用合併排序法對兩個子序列遞歸地;
- 合併:合併兩個已排序的子序列以得到排序結果。
其實可以說的通俗點,合併排序分爲兩個部分,核心部分就是合併,另一部分是合併排序。合併部分就是假設你手中有兩摞已經排好序的撲克牌,牌面朝上,上面小下面大。然後每次比較兩摞牌最上面的一張,取較小的一張面朝下放到新的一摞牌中。這樣,在新的一摞牌中就實現了將兩摞排好序的牌合成一摞。
另一部分是合併排序。作爲遞歸的算法,合併排序必須對自己有信心——有信心能夠完成排序,因爲他要調用自己,對自己沒有信心怎麼調用自己呀。當然它的信心也是有依據的,它在內部實現中會將待排序的數分解成兩份,對每份再次調用合併排序。當分解到足夠小,也就是每份中只有一個數時,它自然就是排好序的了。這樣,合併排序在內部就能夠實現將待排序的數編程兩份排好序的數,用剛纔說到的合併處理一下,就編程一份排好序的數了。
算法性能
穩定性: 穩定排序
時間複雜度: O(nlogn)
空間複雜度: O(n)
僞代碼
合併部分。MERGE過程的時間代價是O(n),在下面僞代碼中,A是數組,p、q、r是下標,滿足p≤q<r。該過程假設A[p..q]和A[q+1..r]都已排好序,並將它們合併成一個已排好序的子數組代替當前子數組A[p..r]。
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+2] <- ∞
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]
then A[k] <- R[j]
j <- j + 1
下面是合併排序的僞代碼,它對子數組A[p..r]進行排序
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語言實現
merge函數實現如下:
void merge(int * array, int head, int middle, int tail){
int n1 = middle - head + 1;
int n2 = tail - middle;
int * L = malloc(n1 * sizeof(int));
int * R = malloc(n2 * sizeof(int));
int i = 0, j = 0;
for ( i = 0; i < n1; ++i) {
L[i] = array[head + i];
}
for ( j = 0; j < n2; ++j) {
R[j] = array[middle + 1 + j];
}
i = 0;
j = 0;
for ( int k = head; k < tail + 1; k++) {
if ( i == n1 ) {
array[k] = R[j++];
}
else if ( j == n2 ) {
array[k] = L[i++];
}
else if ( L[i] <= R[j]) {
array[k] = L[i++];
}
else if ( L[i] > R[j] ) {
array[k] = R[j++];
}
}
free(L);
free(R);
}
有種說法叫過早優化是萬惡之源,所以這裏我沒有對內存申請部分做優化,其實實現時可以做一點策略,當n1和n2小於一定值時使用固定分配內存,這樣可以減少malloc過程的時間消耗。
merge_sort實現如下:
void merge_sort(int *array, int head, int tail){
if ( head < tail ) {
int middle = (head + tail) / 2;
merge_sort(array, head, middle);
merge_sort(array, middle + 1, tail);
merge(array, head, middle, tail);
}
}