白話解析歸併排序

    簡單來說,歸併排序主要是將多個有序的序列合併爲一個有序的序列.

    我們首先看下2個簡單的數 5 , 2  ,那麼直接將5和2比較交換位置就變成 2,5 。然後再加2個數 4,3 ,同理,比較交換變成 3,4 。 

    現在我們將2,5 和 3,4 這2個序列合併爲一個有序序列, 對於沒有什麼算法基礎的同學第一個想到可能是先找出最小的數,然後是第二小的數,第三小的數等等,最後依次將其放入一個新數組中即可完成。

    但是今天我們來討論另一種方案 , 我們先將2個序列的第一個數2和3進行比較選出較小的2放進新數組,然後將第一個序列下標後移一位爲5繼續和3比較選出較小的3放進新數組,再將第二個序列下標後移一位爲4繼續和5比較選出較小的4放進新數組,最後將5放進新數組,這樣新數組的數據就爲 2,3,4,5 的有序序列。這就叫做 歸併排序。

現在我們來看下歸併排序的代碼:

public static void mergeSort(int arr[],int left,int right){
        if(left>=right)return;

        int mid=(right+left)/2;

        mergeSort(arr,left,mid);
        mergeSort(arr,mid+1,right);

        merge(arr,left,mid,right);

    }

    private static void merge(int[] arr, int left, int mid, int right) {

        int tmp[]=new int[right-left+1];
        int leftIndex=left;
        int rightIndex=mid+1;
        int tmpIndex=0;

        while (leftIndex<=mid && rightIndex<=right){
            if(arr[leftIndex]<=arr[rightIndex]){
                tmp[tmpIndex++]=arr[leftIndex++];
            }else{
                tmp[tmpIndex++]=arr[rightIndex++];
            }
        }

        int start=leftIndex,end=mid;

        if(rightIndex<=right){
            start=rightIndex;
            end=right;
        }

        while (start<=end){
            tmp[tmpIndex++]=arr[start++];
        }

        for(leftIndex=0;leftIndex<=right-left;leftIndex++){
            arr[left+leftIndex]=tmp[leftIndex];
        }
    }

    mergeSort 方法中的 arr 爲排序的數組, left 和 right 分別爲arr數組 的左邊界下標和右邊界下標,這裏是採用分治的方式對數組遞歸,然後進行歸併排序的操作。也就是對數組不斷進行二分,直至左邊界大於等於右邊界不能二分爲止。

  現在我們來看下數組 5, 3, 2, 6, 4, 1 的歸併執行過程,其中合併就是指的是執行merge方法:

                                                 5 3 2 6 4 1                 left=0   right=5
                    第一次遞歸                5 3 2                     left=0   right=2
                    第二次遞歸                  5 3                      left=0   right=1
                    第三次遞歸                   5                        left=0   right=0  left>=right,達到終止條件返回
                    第四次遞歸                   3                        left=1   right=1  left>=right,達到終止條件返回
                    第一次合併           3 5 2 6 4 1                 left=0  mid=0 right=1
                    第五次遞歸                   2                        left=2   right=2  left>=right,達到終止條件返回
                    第二次合併           2 3 5 6 4 1                 left=0  mid=1 right=2
                    第六次遞歸                6 4 1                     left=3   right=5
                    第六次遞歸                 6 4                       left=3   right=4
                    第六次遞歸                  6                         left=3   right=3  left>=right,達到終止條件返回
                    第七次遞歸                  4                         left=4   right=4  left>=right,達到終止條件返回
                    第三次合併           2 3 5 4 6 1                 left=3  mid=3 right=4
                    第八次遞歸                  1                         left=5   right=5  left>=right,達到終止條件返回
                    第四次合併           2 3 5 1 4 6                 left=3  mid=4 right=5
                    第五次合併           1 2 3 4 5 6                 left=0  mid=2 right=5

 

    上面就是執行歸併排序的詳細過程了,標紅是歸併過程中發生的數據交換。

    現在我們來詳細解析一下merge方法,以上面的最後一次歸併爲例,我們看下merge的執行過程:

     首先會創建一個 tmp 數組,用於存放已經排序的數據。然後以 mid 爲中心分爲左右2個子數組,分別用 leftIndexrightIndex 2個下標進行遍歷,選出較小的那個數,將其對應的下標加1,循環往復,直至遍歷到終點爲止。最後將子數組中剩餘的數據放進tmp數組中,將tmp中的數據複製進原數組 arr 中即可。

 

歸併優化:

    針對merge方法,還有一種優化的方式:

private static void mergeGuard(int[] arr, int left, int mid, int right) {
        int[] leftArr = new int[mid - left + 2];
        int[] rightArr = new int[right - mid + 1];

        for (int i = 0; i <= mid - left; i++) {
            leftArr[i] = arr[left + i];
        }
        // 第一個數組添加哨兵(最大值)
        leftArr[mid - left + 1] = Integer.MAX_VALUE;

        for (int i = 0; i < right - mid; i++) {
            rightArr[i] = arr[mid + 1 + i];
        }
        // 第二個數組添加哨兵(最大值)
        rightArr[right-mid] = Integer.MAX_VALUE;

        int i = 0;
        int j = 0;
        int k = left;
        while (k <= right) {
            // 當左邊數組到達哨兵值時,i不再增加,直到右邊數組讀取完剩餘值,同理右邊數組也一樣
            if (leftArr[i] <= rightArr[j]) {
                arr[k++] = leftArr[i++];
            } else {
                arr[k++] = rightArr[j++];
            }
        }
    }

   這個主要是將左右數組的邊界分別存儲整型的最大值,這樣在歸併的時候,就可以將左右數組中的所有數據都放進arr中,不需要在數據比較完成後,額外將邊界數據放入新數組中。

 

總結:

    歸併排序,本質上就是採用分治的方式,將序列一點點拆分爲一個個數,最後進行合併。但是人的思維都是正向的,將一個個數合併爲一個有序序列時很容易理解,逆向將序列拆分再合併就比較難理解一點。本文先採用拆分的方式,帶大家理解歸併的核心,然後帶領大家一步步的去深入理解歸併排序。

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