排序(六)歸併排序

本節主要簡單介紹一下歸併排序算法,歸併排序(MERGE-SORT)是建立在歸併操作上的一種有效的排序算法,該算法是採用分治法(Divide and Conquer)的一個非常典型的應用。將已有序的子序列合併,得到完全有序的序列;即先使每個子序列有序,再使子序列段間有序。若將兩個有序表合併成一個有序表,稱爲二路歸併。摘自百度百科

二、分析

在「數據結構與算法分析」中是這麼描述歸併排序算法的:如果N=1,那麼只有一個元素需要排序,答案是顯然的。否則,遞歸地將前半部分數據和後半部分數據各自歸併排序,得到排序後的兩部分數據,然後使用我們的合併算法將這兩部分合併到一起。

不過我們需要注意的是,並不是將序列遞歸拆分直到每部分數據全部N=1,才能開始進行合併的哦。下面我們就分別來介紹下遞歸拆分和合並的方法:

(1)拆分

拆分的方法很簡單,我們以數組array{12, 9, 13, 17, 10, 7, 13, 18}爲例,這是我們的拆分圖示,根據圖片我們知道第1次拆分,我們將序列拆分爲array1{12, 9, 13, 17}和序列array2{10, 7, 13, 18};第二次拆分,我們將序列分爲array1{12, 9}、array2{13, 17}、array3{10, 7}和array4{13, 18};第三次拆分,我們將序列分爲array1{12}、array2{9}、array3{13}、array4{17}、array5{10}、array6{7}、array7{13}和array8{18},到此,我們的拆分結束

在這裏插入圖片描述

(2)合併方法

合併方法就是:將兩個已排序的表,輸出到第三個表中。在這裏我們就以序列array1{9, 12, 13, 17}和array2{7, 10, 13, 18}爲例,首先我們需要創建一個臨時輸出數組array3,其中array3.length = array1.length+array2.length,隨後我們定義三個計數器Apos,Bpos,Cpos,分別表示其在各數組內的下標

在這裏插入圖片描述

首先我們比較array1[Apos] 與array2[Bpos]的大小,隨後將小的數拷貝到array3中,並將相應數組下標往後挪1,本例中即比較數字9 與數字7的大小,隨後將7 拷貝到array3中,並加Bpos+1、Cpos+1

在這裏插入圖片描述

同理,我們使用相同的方法進行下一次的操作

在這裏插入圖片描述

同理,我們使用相同的方法進行下一次的操作

在這裏插入圖片描述

同理,我們使用相同的方法進行下一次的操作

在這裏插入圖片描述

同理,我們使用相同的方法進行下一次的操作,在這裏我們需要注意的是當前半部分和後半部分元素相等,爲了穩定性,我們需要將前半部分的數字拷貝到臨時數組裏

在這裏插入圖片描述

同理,我們使用相同的方法進行下一次的操作

在這裏插入圖片描述

同理,我們使用相同的方法進行下一次的操作後,此時Apos == array1.length,所以比較結束,我們只需要將array2中剩餘的元素全部拷貝到臨時輸出數組中即可,在本例中選取的數字比較特殊,當array1拷貝完畢後,array2只剩一個元素

在這裏插入圖片描述

最後我們將array2中剩餘的元素拷貝到臨時輸出數組中;最後我們還需要將臨時數組中的數據覆蓋到原數據上

在這裏插入圖片描述

在這裏我們是以序列array1{9, 12, 13, 17}和array2{7, 10, 13, 18}爲例,以(1)中拆分後的數組爲例同樣如此:

我們使用同樣的方法將array1{12}和array2{9}合併成爲array1{9, 12};我們將array3{13}和array4{17}合併成array2{13, 17};我們將array5{10}和array6{7}合併成array3{7, 10};我們將array7{13}和array8{18}合併成array4{13, 18}

之後我們將array1{9, 12}和array2{13, 17}合併成array1{9, 12, 13, 17};將array3{7, 10}和array4{13, 18}合併成array2{7, 10, 13, 18}

最後我們將數組array1{9, 12, 13, 17}和array2{7, 10, 13, 18}合併成數組array{7, 9, 10, 12, 13, 13, 17, 18}

三、代碼展示

這是我們歸併排序的代碼

    /**
     * 歸併排序
     * @param array 原始數組,即待排序數組
     */
    private static void mergeSort(int[] array) {
        int length = array.length;
        // 創建一個臨時數組
        int[] tempArray = new int[length];
        mergeSort(array, tempArray, 0, length-1);
    }

通過遞歸方法,將不斷分解出來的前後兩部分各自進行合併運算

    /**
     * 通過遞歸方法,將不斷分解出來的前後兩部分各自進行合併運算
     * @param array 原始數組,即待排序數組
     * @param tempArray 創建的臨時數組,array.length == tempArray.length
     * @param left 左端下標
     * @param right 右端下標
     */
    private static void mergeSort(int[] array, int[] tempArray, int left, int right) {
        // 待處理數組數量大於1
        if(left < right){
            int center = (left + right)/2;
            mergeSort(array, tempArray, left, center);
            mergeSort(array, tempArray, center+1, right);
            merge(array, tempArray, left, center+1, right);
        }
    }

這是基本的合併算法,即將兩個已經排序的表,輸出放到第三個表中;最後再將排序好的臨時數組數據覆蓋到原始數組上,到此,歸併排序結束

    /**
     * 這是基本的合併算法,即將兩個已經排序的表,輸出放到第三個表中
     * @param array 原始數組
     * @param tempArray 創建的臨時數組
     * @param leftPos 前半部分的起始下標 leftPosition
     * @param rightPos 後半部分的起始下標 rightPosition
     * @param rightEnd 後半部分的結束下標 rightEndPosition
     */
    private static void merge(int[] array, int[] tempArray, int leftPos, int rightPos, int rightEnd) {
        // 前部部分的結束下標
        int leftEnd = rightPos - 1;
        // 臨時數組的要添加元素的起始下標
        int tempPos = leftPos;
        // 臨時數組添加元素的總個數,即前半部分和後半部分元素的總數
        int numElements = rightEnd - leftPos + 1;
        // 前後兩部分都有元素沒有拷貝完畢
        while (leftPos <= leftEnd && rightPos <= rightEnd) {
            if(array[leftPos] <= array[rightPos]){
                // 將前半部分小的數拷貝到臨時數組裏,隨後下標往後挪
                tempArray[tempPos++] = array[leftPos++];
            }else{
                tempArray[tempPos++] = array[rightPos++];
            }
        }
        // 當後半部分已經全部拷貝完畢,把前半部分剩餘的元素直接拷貝到臨時數組即可
        while(leftPos <= leftEnd) {
            tempArray[tempPos++] = array[leftPos++];
        }
        while(rightPos <= rightEnd) {
            tempArray[tempPos++] = array[rightPos++];
        }
        // 將臨時數組裏的數據拷貝並覆蓋到原始數組相應位置即可
        for(int i=0; i<numElements; i++,rightEnd--){
            array[rightEnd] = tempArray[rightEnd];
        }
    }

四、總結

當然代碼編寫並不是固定的,肯定會有很多變形格式,其實我們研究的也是其歸併的思想和每種寫法的效率問題。如果想要查看更多算法基礎,去我的博客目錄裏查看吧,因爲關於每塊知識點的介紹,博客單節寫的比較零散,不容易查找。

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