記一次歸併排序調優

歸併排序核心思想:

合併兩個有序數組爲一個有序數組是很輕鬆的。

步驟:

  1. 將一個數組不斷的從中間分成兩個數組
  2. 對分後的兩個數組不斷重複步驟一,直到數組的長度爲1
  3. 對分成的兩個數組進行合併,此時就是分成的兩個數組都已經是有序的

這段時間複習了一下數據結構的知識,對歸併排序進行了重新的編寫,採用的java語言,但通過測試20萬個隨機數據發現,歸併排序的效率和選擇排序的效率竟然差不多,對此我陷入了深深的懷疑,究竟是哪個地方出錯了呢?
在這裏插入圖片描述
在此,先貼出原代碼:

public int[] mergeSort(int[] array) {

        //採取先分後治的思想
        if(array.length > 1){

            //分
            int[] left = mergeSort(leftArray(array));
            int[] right = mergeSort(rightArray(array));
            //合
            array = mergeArray(left,right);
        }
        return array;
    }

    //獲取數組的左半邊
    private int[] leftArray(int[] a){
        int[] b = new int[(a.length + 1)/2];
        for (int i = 0; i < b.length; i++) {
            b[i] = a[i];
        }
        return b;
    }

    //獲取數組的右半邊
    private int[] rightArray(int[] a){
        int[] b = new int[a.length/2];
        int lenA = (a.length + 1) / 2;
        for (int i = 0; i < b.length; i++) {
            b[i] = a[i + lenA];
        }
        return b;
    }

    //合併兩有序數組
    private int[] mergeArray(int[] left, int[] right){
        int[] a = new int[left.length + right.length];
        int indexL = 0;
        int indexR = 0;
        int lenL = left.length;
        int lenR = right.length;
        for (int i = 0; i < a.length; i++) {
            a[i] = left[indexL]  < right[indexR] ? left[indexL++] : right[indexR++];
            if(indexL == lenL || indexR == lenR){
                //滿足條件跳出循環
                break;
            }
        }
        if(indexL == lenL){
            //將右側數組依次裝進合併數組內
            for (int i = indexL + indexR; i < a.length; i++) {
                a[i] = right[indexR++];
            }
        }else if(indexL == indexR){
            //將左側數組依次裝進合併數組內
            for (int i = indexL + indexR; i < a.length; i++) {
                a[i] = left[indexL++];
            }
        }
        return a;
    }

經過思索可能造成速度緩慢的因素(打死我都不認爲是歸併的思想出了問題),在共產黨的帶領下,我揪出了那個問題的兇手,沒錯,兇手只有一個,那就是不斷的申請新數組,開闢空間是很耗時的,難怪效率那麼慢了,原來大部分的時間都用來開礦了!慚愧,慚愧,這就是當代程序員濫用空間的血淋淋的例子,懷念遠古時期,前輩們用個變量都要思考着個變量是否必要使用,生怕造成一點空間的浪費,額,扯遠了。。。

知道了問題的原因,那就要着手去解決這個問題,既然開闢空間耗時,那就不開闢空間了,儘量用原來的數組空間,頂多加個臨時數組,那這樣問題能得到解決嗎?說實話,我現在也不知道運行起來效率能提升多少,但提升是肯定的,期待中…

修改後的代碼,如下:

public int[] mergeSort(int[] array) {
        if(array == null || array.length == 0){
            return null;
        }
        //該temp爲了減少doMerge中的創建臨時數組造成的性能下降而提前設置的
        int[] temp = new int[array.length];
        merge(array, 0, array.length - 1, temp);
        return array;
    }

    private void merge(int[] array, int start, int end, int[] temp){
        if(start < end){
            //分
            int mid = (start + end) >> 1;
            merge(array, start, mid, temp);
            merge(array, mid + 1, end, temp);
            //治
            doMerge(array,start,mid,end, temp);
        }
    }

    private void doMerge(int[] array, int start, int mid, int end, int[] temp){
        int left = start;
        int right = mid + 1;
        int index = start;
        while(left <= mid && right <= end){
            temp[index++] = array[left] > array[right] ? array[left++] : array[right++];
        }

        //將剩餘一邊的數組全部依次導入
        while(left <= mid){
            temp[index++] = array[left++];
        }

        while (right <= end){
            temp[index++] = array[right++];
        }

        //將臨時數組導入array
        for (int i = start; i <= end; i++) {
            array[i] = temp[i];
        }
    }

話不多說,有什麼問題跑起來再說,噹噹噹,結果如下:
在這裏插入圖片描述
OMG!怎麼會這樣,啊啊啊啊啊啊啊啊!

我一定是看錯了,不行我要去看眼科,什麼情況啊,我改動了那麼大,怎麼會一點變化都沒有,而且跟選擇排序時間基本一樣。

我好像,發現了什麼,跟選擇排序的時間基本一樣=.=

呵呵,歸併排序調用了選擇排序
在這裏插入圖片描述
辣眼睛,=.=

改回來吧,看看改後的歸併排序效率有多高
在這裏插入圖片描述

再對比下,改之前的歸併排序時間
在這裏插入圖片描述
唔,證明我的改動還是相當讚的,20完個數據快了80%,現在加大數據量,看看效果如何。

先看老版的一億個隨機數的排序時間:
在這裏插入圖片描述

在看新版的一億個隨機數的排序時間
在這裏插入圖片描述

唔,大體這樣了,超大數據的時候快了25%吧,還是沒達到我的預期,我覺得起碼要快兩倍呢,桑心

最後給一點建議,如果有同學想理解歸併排序的思想,可以看老版代碼,如果想更快的速度,可以看新版代碼

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