歸併排序MergeSort的C實現

小明的賢魚發表於2019-05-14

歸併排序作為最經典的分治演算法之一,本質是利用遞迴把問題分解至最小子問題(即將原陣列分解為只有單個元素的子陣列),然後遞迴開始“回升”,每一層回升都是在合併兩個有序陣列(依次從兩個陣列的頭部取出較小的元素放入目標陣列,某一組全部取出後即可直接依次插入另一陣列的所有剩餘元素,兩組元素全部取完後排序結束),C語言程式碼如下:

void merge(int arrA[], int lenA, int arrB[], int lenB, int arrTarget[]) {
    int iA = 0, iB = 0, iT = 0;
    for (; iA < lenA && iB < lenB; iT ++) { // 當兩個陣列都還有剩餘元素時,比較每一個元素並插入
        if (arrA[iA] <= arrB[iB]) {
            arrTarget[iT] = arr[iA];
            iA ++;
        } else {
            arrTarget[iT] = arr[iB];
            iB ++;
        }
    }
    for (; iA < lenA; iA ++, iT ++) arrTarget[iT] = arrA[iA]; // 若陣列A還有剩餘元素
    for (; iB < lenB; iB ++, iT ++) arrTarget[iT] = arrA[iB]; // 若陣列B還有剩餘元素
}

完成了這個merge,其實就已經做完了歸併排序的大部分工作,接下來我們只要把前期準備做好就可以了!也就是merge接受的兩個輸入陣列必須是有序的,但是怎麼才能從亂序的原始陣列中得到兩個有序的子陣列呢?
這裡我們可以使用二分法,也就是將原始陣列從中點開始不斷往下分割,當每一個子陣列都只包含一個元素時,它也就達到了有序狀態。而這裡,自然也就是遞迴開始“回升”的節點,merge也可以出現了。
於是整個排序的過程就是:不斷二分 -> 觸底(完全分割) -> 回升

void mergeSort(int arr[], int fst, int lst, int arrTarget[]) {
    if (fst < lst) {
        // 二分(此處的函式將會在子陣列的fst不大於lst時開始逐層返回,進行合併)
        int mid = (fst + lst) / 2;
        mergeSort(arr, fst, mid, arrTarget);
        mergeSort(arr, mid + 1, lst, arrTarget);
        // 合併
        merge(arr + fst, mid - fst + 1, arr + mid + 1, lst - mid, arrTarget + fst);
    }
}

執行結束後,arrTarget就是原陣列的有序版本,它可以在main函式中直接定義,不過也可以通過進一步打包函式來避免這種麻煩並減少需要填寫的引數:

int* MergeSort(int arr[], int len) {
    int* arrTarget = (int*)malloc(sizeof(int) * len);
    // 此處不建議直接建立陣列,函式結束後所有臨時變數的記憶體都將被釋放,任何操作都可能改變這裡的資料
    if (arrTarget == NULL) { // 記憶體申請失敗
        printf("Error: malloc failed.
");
        exit(1);
    }

    mergeSort(arr, 0, len - 1, arrTarget);

    return arrTarget;
}

現在只需要填上原始陣列和長度就可以得到一個排好序的新陣列的指標啦!

相關文章