1. 程式人生 > >演算法 歸併排序小述

演算法 歸併排序小述

一、概述

本節主要簡單介紹一下歸併排序演算法,歸併排序(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];
        }
    }

四、總結

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