歸併排序

歸併排序是建立在歸併操作上的一種有效的排序算法。該算法是採用分治法(Divide and Conquer)的一個非常典型的應用。


首先考慮下如何將將二個有序數列合併。這個非常簡單,只要從比較二個數列的第一個數,誰小就先取誰,取了後就在對應數列中刪除這個數。然後再進行比較,如果有數列爲空,那直接將另一個數列的數據依次取出即可。


再來看歸併排序


描述:

基本思路就是將數組分成二組A,B,如果這二組組內的數據都是有序的,那麼就可以很方便的將這二組數據進行排序。如何讓這二組組內數據有序了?


可以將A,B組各自再分成二組。依次類推,當分出來的小組只有一個數據時,可以認爲這個小組組內已經達到了有序,然後再合併相鄰的二個小組就可以了。這樣通過先遞的分解數列,再合數列就完成了歸併排序。


圖示:



算法:

package com.zq.algorithm.sort;

/**
 * Created by zhengshouzi on 2015/11/2.
 */
public class MergeSort {
    public static void main(String[] args) {

        int[] a = {10, 4, 6, 3, 8, 2, 5, 7};

        mergeSort(a, 0, a.length - 1);

        //輸出最後結果
       for (int i = 0; i < a.length; i++) {
            System.out.print(" " + a[i]);
        }
    }

    /**
     * 遞歸方法,用來分解問題,
     * @param a     源數組
     * @param left  每次分解的序列的左邊界
     * @param right 每次分解的序列的的右邊界
     */
    public static void mergeSort(int[] a, int left, int right) {
        //定義中間值
        int middle;
        
        //定義遞歸結束條件,,當左邊界< 右邊界的時候才遞歸,左邊界等於右邊界 時候表示這一次只歸併一個元素,一個元素當然是有序的,不用做處理直接返回上一層
        if (left < right) {
            //求出中間值
            middle = (left + right) / 2;
            //遞歸調用此方法,使原來的大問題,分解成最小的一個元素有序的小問題,
            mergeSort(a, left, middle);
            mergeSort(a, middle + 1, right);
            //當左邊和右邊都有序了,那麼就合併左右兩邊的序列,使序列整體有序
            mergeArray(a, left, middle, right);
        }

    }
    /**
     *   歸併兩個連續的有序的序列
     * @param a 源數組
     * @param first 序列的開始下標
     * @param middle 中間下標
     * @param last 結束下標
     */
    public static void mergeArray(int[] a, int first, int middle, int last) {

        //必須要一個temp數組,長度爲每次需要歸併的序列的長度,也就是動態變化的,最大爲整個a 數組的長度(最後一個合併)
        int[] temp = new int[last - first + 1];
        //定義下標變量
        int index1 = first;
        int index2 = middle + 1;
        int k = 0;
        //將兩個有序的序列,合併成一個有序的序列,放入temp數組中,循環退出條件是:有一個序列比較完了,那麼這時候將另外一個有序序列中所有元素依次放入temp中
        while (index1 <= middle && index2 <= last) {
            //依次比較兩個序列中元素誰小,誰就放入temp中,
            if (a[index1] <= a[index2]) {
                temp[k++] = a[index1++];
            } else {
                temp[k++] = a[index2++];
            }
        }

        //將上面沒有比較晚的,有序序列中所有元素依次放入temp中
        while (index1 <= middle)
            temp[k++] = a[index1++];
        while (index2 <= last)
            temp[k++] = a[index2++];

        //temp必是有序的,將temp中的元素,寫到原來的a數組中的對應位置(從first 開始的temp.length 個長度)
        for (int i = 0; i < temp.length; i++) {
            a[first + i] = temp[i];
            //System.out.print(" " + temp[i]);
        }
        //System.out.println();
    }
}


上面的代碼都配有詳細的註釋,不懂的可以評論給我。


下面的代碼,我打印出了整個分解和合並的過程,如下圖

package com.zq.algorithm.sort;

/**
 * Created by zhengshouzi on 2015/11/2.
 */
public class MergeSort {
    public static void main(String[] args) {

        int[] a = {10, 4, 6, 3, 8, 2, 5, 7};

        mergeSort1(a, 0, a.length - 1);

        //輸出最後結果
       for (int i = 0; i < a.length; i++) {
            System.out.print(" " + a[i]);
        }
    }

    public static void mergeSort1(int[] a, int left, int right) {
        //定義中間值
        int middle;

        System.out.print("分解:");
        for (int i = left; i <= right; i++) {
            System.out.print( "  "+ a[i]);
        }
        System.out.println();

        //定義遞歸結束條件,,當左邊界< 右邊界的時候才遞歸,左邊界等於右邊界 時候表示這一次只歸併一個元素,一個元素當然是有序的,不用做處理直接返回上一層
        if (left < right) {
            //求出中間值
            middle = (left + right) / 2;

            //遞歸調用此方法,使原來的大問題,分解成最小的一個元素有序的小問題,
            mergeSort1(a, left, middle);
            mergeSort1(a, middle + 1, right);
            //當左邊和右邊都有序了,那麼就合併左右兩邊的序列,使序列整體有序
            mergeArray1(a, left, middle, right);
        }else {
            //System.out.println("葉子節點:" +a[left]);
        }

    }


    public static void mergeArray1(int[] a, int first, int middle, int last) {

        //必須要一個temp數組,長度爲每次需要歸併的序列的長度,也就是動態變化的,最大爲整個a 數組的長度(最後一個合併)
        int[] temp = new int[last - first + 1];
        //定義下標變量
        int index1 = first;
        int index2 = middle + 1;
        int k = 0;
        //將兩個有序的序列,合併成一個有序的序列,放入temp數組中,循環退出條件是:有一個序列比較完了,那麼這時候將另外一個有序序列中所有元素依次放入temp中
        while (index1 <= middle && index2 <= last) {
            //依次比較兩個序列中元素誰小,誰就放入temp中,
            if (a[index1] <= a[index2]) {
                temp[k++] = a[index1++];
            } else {
                temp[k++] = a[index2++];
            }
        }

        //將上面沒有比較晚的,有序序列中所有元素依次放入temp中
        while (index1 <= middle)
            temp[k++] = a[index1++];
        while (index2 <= last)
            temp[k++] = a[index2++];

        //temp必是有序的,將temp中的元素,寫到原來的a數組中的對應位置(從first 開始的temp.length 個長度)
        System.out.print("歸併:");
        for (int i = 0; i < temp.length; i++) {
            a[first + i] = temp[i];
            System.out.print(" " + temp[i]);
        }
        System.out.println();
        System.out.println("--------------------------------------------");
    }
}


上面的數字,是計算機執行歸併過程的調用執行順序,對於理解遞歸很有幫助


歸併排序的效率是比較高的,設數列長爲N,將數列分開成小數列一共要lgN(計算機裏面表示以2爲底N的對數)步,每步都是一個合併有序數列的過程,時間複雜度可以記爲O(N),故一共爲O(N*logN)


爲什麼是lgN ??

設需要X步,最後才能分解成一個元素,那麼(N/2的x次方) = 1, 解得X = lgN;



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