【資料結構與演算法】歸併排序

gonghr發表於2021-08-08

概念

過程

  • 分解:將n 個元素分成個含n/2 個元素的子序列;
  • 解決:對兩個子序列遞迴地排序
  • 合併:合併兩個已排序的子序列以得到排序結果

和快排不同的是

  • 歸併的分解較為隨意
  • 重點是合併
  • 需要額外開闢陣列空間

image

image

程式碼實現

    public static void mergeSort(int[] arr){
        if(arr == null||arr.length<2) return; //陣列元素至少為2
        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;  //輔助陣列下標
        int p1 = L;  //第一個待合併陣列起點
        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 (int j = 0; j < help.length; j++) {  //拷貝回原陣列
            arr[L + j]=help[j];
        }
    }

複雜度分析

套用master公式T(N) = a*T(N/b) + O(N^d)
a=2,b=2,d=1
log(b,a) = d -> 時間複雜度為O(N^d * logN) =O(NlogN)
額外空間複雜度 O(N)

所以:

  • 時間複雜度:O(NlogN)
  • 空間複雜度:O(N)
  • 穩定性:穩定
  • 非原址排序

經典例題

劍指 Offer 51. 陣列中的逆序對

在陣列中的兩個數字,如果前面一個數字大於後面的數字,則這兩個數字組成一個逆序對。輸入一個陣列,求出這個陣列中的逆序對的總數。

示例 1:
輸入: [7,5,6,4]
輸出: 5
 
限制:
0 <= 陣列長度 <= 50000

稍微修改一下歸併排序程式碼即可

	class Solution {
        private int ans = 0;     //宣告例項變數
        public int reversePairs(int[] nums) {
            mergeSort(nums, 0, nums.length - 1);
            return ans;
        }
        public void mergeSort(int[] arr, int low, int high) {
            if (arr.length < 2 || low >= high) return;
            int mid = low + ((high - low) >> 1);
            mergeSort(arr, low, mid);
            mergeSort(arr, mid + 1, high);
            merge(arr, low, mid, high);
        }
        public void merge(int[] arr, int low, int mid, int high) {
            int[] helper = new int[high - low + 1];
            int i = low;
            int j = mid + 1;
            int k = 0;
            while (i <= mid && j <= high) {
                if (arr[i] <= arr[j]) {
                    helper[k++] = arr[i++];
                } else {
                    ans += mid - i + 1;   //注意:如果arr[i]>arr[j]則i~mid這個區間的所有數都大於arr[j],因為[low……mid]和[mid+1……high]是已經排好序的。
                    helper[k++] = arr[j++];
                }
            }
            while (i <= mid) {
                helper[k++] = arr[i++];
            }
            while (j <= high) {
                helper[k++] = arr[j++];
            }
            for (int m = 0; m < helper.length; m++) {
                arr[low + m] = helper[m];
            }
        }
    }

相關文章