用Java寫演算法之歸併排序

來源:互聯網
上載者:User

標籤:遞迴   合并演算法   移動   dex   缺陷   相對   對象   container   ace   

轉自;http://flyingcat2013.blog.51cto.com/7061638/1281026

 

前面的三種排序演算法(冒泡排序,選擇排序,插入排序)在平均情況下均為O(n^2)複雜度,在處理較大資料的時候比較吃力。現在來說說相對快速一些的演算法,例如下面的歸併排序。

 

演算法概述/思路

歸併排序是基於一種被稱為“分治”(divide and conquer)的策略。其基本思路是這樣的:

1.對於兩個有序的數組,要將其合并為一個有序數組,我們可以很容易地寫出如下代碼:

 

123456789101112131415161718 //both a and b is ascend.public void merge(int[] a, int[] b, int[] c){    int i=0,j=0,k=0;    while (i<=a.length && j<=b.length){        if (a[i]<=b[i]){            c[k++]=a[i++];        }        else{            c[k++]=b[j++];        }    }    while (i<=a.length){        c[k++]=a[i++];     }    while (j<=b.length){        c[k++]=b[j++];    }}

容易看出,這樣的合并演算法是高效的,其時間複雜度可達到O(n)。

2.假如有一個無序數組需要排序,但它的兩個完全劃分的子數組A和B分別有序,藉助上述代碼,我們也可以很容易實現;

3.那麼,如果A,B無序,怎麼辦呢?可以把它們再分成更小的數組。

4.如此一直劃分到最小,每個子數組都只有一個元素,則可以視為有序數組。

5.從這些最小的數組開始,逆著上面的步驟合并回去,整個數組就排好了。

總而言之,歸併排序就是使用遞,先分解數組為子數組,再合數組。

下面是歸併排序的(圖片來自維基百科):

代碼實現

 

1234567891011121314151617181920212223242526272829303132333435363738 //歸併排序    public static void mergeSort(int[] arr){        int[] temp =new int[arr.length];        internalMergeSort(arr, temp, 0, arr.length-1);    }    private static void internalMergeSort(int[] a, int[] b, int left, int right){        //當left==right的時,已經不需要再劃分了        if (left<right){            int middle = (left+right)/2;            internalMergeSort(a, b, left, middle);          //左子數組            internalMergeSort(a, b, middle+1, right);       //右子數組            mergeSortedArray(a, b, left, middle, right);    //合并兩個子數組        }    }    // 合并兩個有序子序列 arr[left, ..., middle] 和 arr[middle+1, ..., right]。temp是輔助數組。    private static void mergeSortedArray(int arr[], int temp[], int left, int middle, int right){        int i=left;              int j=middle+1;        int k=0;        while ( i<=middle && j<=right){            if (arr[i] <=arr[j]){                temp[k++] = arr[i++];            }            else{                temp[k++] = arr[j++];            }        }        while (i <=middle){            temp[k++] = arr[i++];        }        while ( j<=right){            temp[k++] = arr[j++];        }        //把資料複製回原數組        for (i=0; i<k; ++i){            arr[left+i] = temp[i];        }    }

需要說明的是,在合并數組的時候需要一個temp數組。我們當然有足夠的理由在每次調用的時候重新new一個數組(例如,減少一個參數),但是,注意到多次的建立數組對象會造成額外的開銷,我們可以在開始就建立一個足夠大的數組(等於原數組長度就行),以後都使用這個數組。實際上,上面的代碼就是這麼寫的。

 

演算法效能/複雜度

歸併排序的效率是很高的,由於遞迴劃分為子序列只需要logN複雜度,而合并每兩個子序列需要大約2n次賦值,為O(n)複雜度,因此,只需要簡單相乘即可得到歸併排序的時間複雜度 O(㏒n)。並且由于歸並演算法是固定的,不受輸入資料影響,所以它在最好、最壞、平均情況下表現幾乎相同,均為O(㏒n)。

但是,歸併排序最大的缺陷在於其空間複雜度。從上面的代碼可以看到,在合并子數組的時候需要一個輔助數組,然後再把這個資料拷貝回原數組。所以,歸併排序的空間複雜度(額外空間)為O(n)。可不可以省略這個數組呢?不行!如果取消輔助數組而又要保證原來的數組中資料不被覆蓋,那就必須要在數組中花費大量時間來移動資料。不僅容易出錯,還降低了效率。因此這個輔助空間是少不掉的。

 

演算法穩定性

因為我們在遇到相等的資料的時候必然是按順序“抄寫”到輔助數組上的,所以,歸併排序同樣是穩定演算法。

 

演算法適用情境

歸併排序在資料量比較大的時候也有較為出色的表現(效率上),但是,其空間複雜度O(n)使得在資料量特別大的時候(例如,1千萬資料)幾乎不可接受。而且,考慮到有的機器記憶體本身就比較小,因此,採用歸併排序一定要注意。

用Java寫演算法之歸併排序

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.