算法學習與代碼實現3——合併排序

算法學習與代碼實現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);
    }
}

本文代碼託管於:
https://github.com/haoranzeus/algorithms_study

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章