圖解算法系列之歸併排序

(1)算法描述

對於給定的線性序列,將當前序列不斷的進行分組,當每個分組的數據只有一個元素時,代表這個分組是有序的,那麼向上合併。每一層兩兩合併,合併的過程是,開闢一個新空間,使用兩個指針同時掃描兩個有序的分組,使得較小的元素或者較大的元素先進入新空間。在不斷的比較之後,如果一個分組的元素爲空,直接拷貝另一個沒有被用完的元素到新空間。

(2) 圖解算法

歸併排序的過程

圖解算法系列之歸併排序

合併函數的合併過程

圖解算法系列之歸併排序

(3) C/C++代碼實現

CustomSort.h

// 歸併排序
void MergeSort(int arr[], int number);
// 內部的歸併排序
void __MergeSort(int arr[], int left, int right);
// 內部合併
void __Merge(int arr[], int left, int mid, int right);

CustomSort.cpp

/************************************************************
- 功能描述:實現歸併排序
- int arr[]: 待排序的數組
- int number: 待排序數組中的元素
- 返回值:void
************************************************************/
void MergeSort(int arr[], int number) {
    // 數組爲空或者有一個一下的元素直接返回
    if(arr == NULL || number <= 1) {
        return;
    }

    // 調用內部排序算法
    __MergeSort(arr, 0, number-1);
}

/************************************************************
- 功能描述:實現歸併排序的內部排序
- int arr[]: 待排序的數組
- int left: 待排序數組的左邊界
- int right: 待排序數組的右邊界
- 返回值:void
************************************************************/
void __MergeSort(int arr[], int left, int right) {
    // 如果 right <= left 沒有排序的必要性
    if(right <= left) {
        return;
    }

    // 計算中間值
    // 沒有使用 (right + left) / 2 是爲了避免數據太大導致內存溢出
    int mid = (right - left) / 2 + left;

    // 分別排序兩個分組
    __MergeSort(arr, left, mid);
    __MergeSort(arr, mid+1, right);

    // 排序完成後就合併兩個分組
    __Merge(arr, left, mid, right);
}

/************************************************************
- 功能描述:實現歸併排序的分組合並
- int arr[]: 待排序的數組
- int left: 待合併數組的左邊界
- int right: 待合併數組的右邊界
- int mid: 待合併數組的中間值
- 返回值:void
************************************************************/
void __Merge(int arr[], int left, int mid, int right) {
    int len = right - left + 1;

    // 動態創建數組,因爲每個分組的大小都不一樣,使用完需要delete[]空間
    int *temp = new int[len];

    // 用於第一個分組的指針one
    // 用於第二個分組的指針two
    // 用於輔助數組的指針i
    int one = left;
    int two = mid + 1;
    int i = 0;

    // 判斷只要one和two都沒有越界就不斷的進行比較
    while(one <= mid && two <= right) {
        temp[i++] = arr[one] > arr[two] ? arr[one++] : arr[two++];
    }

    // 判斷哪個數組的指針還沒到頭就直接全都拷貝到temp數組
    while(one <= mid) {
        temp[i++] = arr[one++];
    }
    while(two <= right) {
        temp[i++] = arr[two++];
    }

    // 往回拷貝數組,注意數組的位置
    for(int j = 0; j < len; j++) {
        arr[left + j] = temp[j];
    }

    delete[] temp;
}

(4) Java代碼實現

public class MergeSort {
    // 歸併排序函數
    public static void sort(int[] arr) {
         // 如果數組爲null或者是數組中的元素小於2, 沒有排序的意義
         if (arr == null || arr.length < 2) {
               // 直接返回
               return;
         }
         // 調用排序函數
         mergeSort(arr, 0, arr.length - 1);
    }
    // 主要邏輯
    public static void mergeSort(int[] arr, int l, int r) {
         // 如果左邊的指針等於右邊指針, 也就是數組不能再分割
         if (l == r) {
               // 就要直接返回
               return;
         }
         // 數組中間數值的指針位置
         int mid = l + ((r - l) >> 1);
         // 排序中間位置左邊的數組
         mergeSort(arr, l, mid);
         // 排序中間位置右邊的數組
         mergeSort(arr, mid + 1, r);
         // 合併函數
         merge(arr, l, mid, r);
    }
     // 合併函數
    public static void merge(int[] arr, int l, int m, int r) {
         // 創建輔助數組
         int[] help = new int[r - l + 1];
         // 輔助數組的指針位置
         int i = 0;      
         // 數組1的指針位置
         int p1 = l;
         // 數組2的指針位置
         int p2 = m + 1;
         //  判斷左邊和右邊的數組是否越界
         while (p1 <= m && p2 <= r) {
               // 如果都沒有越界, 向輔助數組添加數據,類似於外排的方式
               help[i++] = arr[p1] < arr[p2] ? arr[p1++] :  arr[p2++];
         }
         // 如果第一個數組的指針還沒到頭, 就要拷貝數組
         while (p1 <= m) {
               help[i++] = arr[p1++];
         }
         // 如果第二個數組的指針還沒到頭, 就要拷貝數組
         while (p2 <= r) {
               help[i++] = arr[p2++];
         }
         // 拷貝排序後數組
         for (i = 0; i < help.length; i++) {
               arr[l + i] = help[i];
         }
    }
}

(5) 時間複雜度分析

當函數出現遞歸調用的時候,一個函數A調用了本身,假定是subA,那麼當前的函數A將會壓入系統的棧內,系統將會保存現場(包括函數執行到哪一行代碼,函數當前的調用狀態以及函數中變量的值),進行下一個函數的執行,經過一層層函數的調用,遇到一個返回條件,系統中的棧中保存的狀態將會一個一個的彈出,也就是函數的恢復現場,最後函數調用結束。
只要符合master公式的都可以使用一下方法計算時間複雜度:
master公式:T(N)=a*T(N/b)+O(Nd)

1) log(b, a) > d 複雜度是O(N log(b,a))
2) log(b, a) = d 複雜度是O(Nd * log(2, N))
3) log(b, a) < d 複雜度是O(Nd)

估計時間複雜度:左側部分的規模和右側部分的規模都是N/2,在整體外排的過程中總共劃過N個數,算式爲:T(N)=2T(N/2)+O(N),代入master公式,複雜度就是O(N*log2N),空間複雜度是O(N)

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