前面的三種排序算法(冒泡排序,選擇排序,插入排序)在平均狀況下均爲O(n^2)複雜度,在處理較大數據的時候比較吃力。如今來講說相對快速一些的算法,例以下面的歸併排序。java
算法概述/思路算法
歸併排序是基於一種被稱爲「分治」(divide and conquer)的策略。其基本思路是這樣的:數組
1.對於兩個有序的數組,要將其合併爲一個有序數組,咱們能夠很容易地寫出以下代碼:ide
//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無序,怎麼辦呢?能夠把它們再分紅更小的數組。spa
4.如此一直劃分到最小,每一個子數組都只有一個元素,則能夠視爲有序數組。對象
5.從這些最小的數組開始,逆着上面的步驟合併回去,整個數組就排好了。排序
總而言之,歸併排序就是使用遞歸,先分解數組爲子數組,再合並數組。遞歸
下面是歸併排序的示意圖(圖片來自維基百科):
代碼實現
//歸併排序 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千萬數據)幾乎不可接受。並且,考慮到有的機器內存自己就比較小,所以,採用歸併排序必定要注意。
參考資料
1.維基百科 http://zh.wikipedia.org/wiki/%E5%BD%92%E5%B9%B6%E6%8E%92%E5%BA%8F