1. 程式人生 > 實用技巧 >看動畫學演算法之:排序-歸併排序

看動畫學演算法之:排序-歸併排序

目錄

簡介

歸併排序簡稱Merge sort是一種遞迴思想的排序演算法。這個演算法的思路就是將要排序的陣列分成很多小的部分,直到這些小的部分都是已排序的陣列為止(只有一個元素的陣列)。

然後將這些排序過的陣列兩兩合併起來,組成一個更大一點的陣列。接著將這些大一點的合併過的陣列再繼續合併,直到排序完整個陣列為止。

歸併排序的例子

假如我們有一個數組:29,10,14,37,20,25,44,15,怎麼對它進行歸併排序呢?

先看一個動畫:

我們來詳細分析一下上面例子的執行過程:

首先將陣列分為兩部分,[29,10,14,37]和[20,25,44,15]。

[29,10,14,37]又分成兩部分[29,10]和[14,37]。

[29,10]又被分成兩部分[29]和[10],然後對[29]和[10]進行歸併排序生成[10,29]。

同樣的對[14,37]進行歸併排序得到[14,37]。

將[10,29]和[14,37]再次進行歸併排序得到[10,14,29,37],以此類推,得到最後的結果。

歸併排序演算法思想

歸併排序主要使用了分而治之的思想。將一個大的陣列分成很多很多個已經排序好的小陣列,然後再對小陣列進行合併。

這個Divide的過程可以使用遞迴演算法,因為不管是大陣列還是小陣列他們的divide邏輯是一樣的。

而我們真正做排序的邏輯部分是在合併這一塊。

歸併排序的java實現

先看一下最核心的merge部分:

   /**
     *合併兩部分已排序好的陣列
     * @param array 待合併的陣列
     * @param low   陣列第一部分的起點
     * @param mid   陣列第一部分的終點,也是第二部分的起點-1
     * @param high  陣列第二部分的終點
     */
    private void  merge(int[] array, int low, int mid, int high) {
        // 要排序的陣列長度
        int length = high-low+1;
        // 我們需要一個額外的陣列儲存排序過後的結果
        int[] temp= new int[length];
        //分成左右兩個陣列
        int left = low, right = mid+1, tempIdx = 0;
        //合併陣列
        while (left <= mid && right <= high) {
            temp[tempIdx++] = (array[left] <= array[right]) ? array[left++] : array[right++];
        }
        //一個數組合並完了,剩下的一個繼續合併
        while (left <= mid) temp[tempIdx++] = array[left++];
        while (right <= high) temp[tempIdx++] = array[right++];
        //將排序過後的陣列拷貝回原陣列
        for (int k = 0; k < length; k++) array[low+k] = temp[k];
    }

大家需要注意的是,我們的元素是存在原始數組裡面的,方法的第一個引數就是原始陣列。

後面的三個引數是陣列中需要歸併排序的index。三個index將陣列劃分成了兩部分:array[low to mid], array[mid+1 to high]。

merge的邏輯就是對這兩個陣列進行合併。

因為我們的陣列本身是存放有原始的,所以要想進行歸併排序,我們需要藉助一個額外的陣列空間int[] temp。

通過比較array[low to mid], array[mid+1 to high]中的元素大小,一個個將元素插入到int[] temp中,最後將排序過後的陣列拷貝回原陣列,merge完成。

然後我們再看一下divide的部分,divide部分實際上就是遞迴呼叫,在遞迴的最後,我們需要呼叫merge方法即可:

    public void doMergeSort(int[] array, int low, int high){
        // 要排序的陣列 array[low..high]
        //使用二分法進行遞迴,當low的值大於或者等於high的值的時候,就停止遞迴
        if (low < high) {
            //獲取中間值的index
            int mid = (low+high) / 2;
            //遞迴前面一半
            doMergeSort(array, low  , mid );
            //遞迴後面一半
            doMergeSort(array, mid+1, high);
            //遞迴完畢,將排序過後的陣列的兩部分合並
            merge(array, low, mid, high);
            log.info("merge之後的陣列:{}",array);
        }
    }

array是原陣列,low和high標記出了要遞迴排序的陣列起始位置。

執行下上面的結果:

可以看到輸出結果和我們動畫展示的結果是一致的。

歸併排序的時間複雜度

我們看下歸併排序的時間複雜度是怎麼樣的。

首先看merge方法,merge方法實際是遍歷了兩個陣列,所以merge方法的時間複雜度是O(N)。

再看一下divide方法:

divide方法將排序分成了logN層,每層都可以看做是對N個元素的合併排序,因此每層的時間複雜度是O(N)。

加起來,總的時間複雜度就是O(N logN)。

本文的程式碼地址:

learn-algorithm

本文已收錄於 http://www.flydean.com/algorithm-merge-sort/

最通俗的解讀,最深刻的乾貨,最簡潔的教程,眾多你不知道的小技巧等你來發現!

歡迎關注我的公眾號:「程式那些事」,懂技術,更懂你!