歸併排序是一種基於分治思想的高效排序算法,它的核心是“分而治之”——先把大問題拆成小問題解決,再把結果合併起來。和冒泡排序、選擇排序這些簡單排序算法相比,歸併排序的時間複雜度更低,適合處理較大規模的數據。

歸併排序的基本思想

歸併排序主要分爲兩個步驟:分解合併

  1. 分解(Divide)
    把待排序的數組從中間分成左右兩部分,然後遞歸地對每一部分重複“分解”操作,直到每一部分只有一個元素(或空數組)。這時候每個子數組已經是“有序”的了(單個元素天然有序)。

舉個例子:
假設我們要排序數組 [5, 3, 8, 6, 2, 7, 1, 4]
第一次分解成 [5,3,8,6][2,7,1,4]
繼續分解左半部分 [5,3,8,6][5,3][8,6],右半部分 [2,7,1,4][2,7][1,4]
再分解 [5,3][5][3][8,6][8][6][2,7][2][7][1,4][1][4]
直到所有子數組都只有一個元素。

  1. 合併(Conquer)
    把兩個已排序的子數組合併成一個更大的有序數組。合併時,我們需要比較兩個子數組的元素,每次取較小的那個放入結果數組,直到其中一個子數組的元素全部處理完畢,再把剩下的元素直接放入結果數組。

比如,現在有兩個有序子數組 [3,5][6,8],合併過程如下:
- 比較 36,取 3 放入結果;
- 比較 56,取 5 放入結果;
- 此時左子數組剩餘 6,8,右子數組已空,直接放入 6,8
- 合併結果爲 [3,5,6,8]

歸併排序的C++實現

下面我們用C++代碼實現歸併排序,主要包含兩個函數:mergeSort(遞歸分解)和 merge(合併兩個有序數組)。

步驟1:合併函數 merge

合併函數的作用是將兩個已排序的子數組合併成一個有序數組。

void merge(vector<int>& arr, int low, int mid, int high) {
    int leftLen = mid - low + 1;  // 左子數組長度
    int rightLen = high - mid;    // 右子數組長度

    // 創建臨時數組存儲左右子數組
    vector<int> left(leftLen), right(rightLen);

    // 複製左子數組到臨時數組left
    for (int i = 0; i < leftLen; i++) {
        left[i] = arr[low + i];
    }
    // 複製右子數組到臨時數組right
    for (int i = 0; i < rightLen; i++) {
        right[i] = arr[mid + 1 + i];
    }

    // 合併兩個臨時數組到原數組arr
    int i = 0, j = 0, k = low;  // i: left指針, j: right指針, k: arr指針
    while (i < leftLen && j < rightLen) {
        if (left[i] <= right[j]) {  // 取較小元素放入arr
            arr[k] = left[i];
            i++;
        } else {
            arr[k] = right[j];
            j++;
        }
        k++;
    }

    // 處理left中剩餘的元素
    while (i < leftLen) {
        arr[k] = left[i];
        i++;
        k++;
    }

    // 處理right中剩餘的元素
    while (j < rightLen) {
        arr[k] = right[j];
        j++;
        k++;
    }
}

步驟2:歸併排序函數 mergeSort

遞歸分解數組,直到子數組長度爲1,然後調用 merge 合併。

void mergeSort(vector<int>& arr, int low, int high) {
    if (low < high) {  // 遞歸終止條件:子數組長度爲1時停止
        int mid = low + (high - low) / 2;  // 避免low+high溢出,等價於 (low+high)/2
        mergeSort(arr, low, mid);          // 遞歸排序左半部分
        mergeSort(arr, mid + 1, high);     // 遞歸排序右半部分
        merge(arr, low, mid, high);        // 合併兩個有序子數組
    }
}

步驟3:測試代碼

main 函數中調用 mergeSort 並驗證排序結果:

#include <iostream>
#include <vector>
using namespace std;

// 合併函數merge(同上)
// ...(此處省略merge函數代碼,實際使用時需完整寫出)

// 歸併排序函數mergeSort(同上)
// ...(此處省略mergeSort函數代碼,實際使用時需完整寫出)

int main() {
    vector<int> arr = {5, 3, 8, 6, 2, 7, 1, 4};
    int n = arr.size();

    cout << "排序前的數組:";
    for (int num : arr) {
        cout << num << " ";
    }
    cout << endl;

    mergeSort(arr, 0, n - 1);  // 調用歸併排序

    cout << "排序後的數組:";
    for (int num : arr) {
        cout << num << " ";
    }
    cout << endl;

    return 0;
}

示例演示:歸併排序的完整過程

以數組 [5, 3, 8, 6, 2, 7, 1, 4] 爲例,排序過程如下:

  1. 分解過程
    - 初始數組:[5, 3, 8, 6, 2, 7, 1, 4]
    - 分解爲 [5,3,8,6][2,7,1,4]
    - 分解 [5,3,8,6][5,3][8,6]
    - 分解 [5,3][5][3],分解 [8,6][8][6]
    - 分解 [2,7,1,4][2,7][1,4]
    - 分解 [2,7][2][7],分解 [1,4][1][4]

  2. 合併過程
    - 合併 [5][3][3,5]
    - 合併 [8][6][6,8]
    - 合併 [3,5][6,8][3,5,6,8](左半部分排序完成)
    - 合併 [2][7][2,7]
    - 合併 [1][4][1,4]
    - 合併 [2,7][1,4][1,2,4,7](右半部分排序完成)
    - 最終合併 [3,5,6,8][1,2,4,7][1,2,3,4,5,6,7,8]

運行代碼後,輸出結果會是排序後的數組 [1, 2, 3, 4, 5, 6, 7, 8],驗證了算法的正確性。

總結

歸併排序通過“分解-合併”的步驟實現排序,時間複雜度爲 O(n log n)(分解需要log n層,每層合併n個元素),空間複雜度爲 O(n)(需要臨時數組存儲合併結果)。它是一種穩定且高效的排序算法,適合處理較大規模的數據。

通過上面的代碼和示例,你應該已經理解了歸併排序的核心邏輯。試着自己動手修改數組,看看不同輸入是否都能正確排序,加深對遞歸和合並過程的理解吧!

小夜