1 思想算法
歸併排序(MERGE-SORT)是利用歸併的思想實現的排序方法,該算法採用經典的分治(divide-and-conquer)策略(分治法將問題分(divide)成一些小的問題而後遞歸求解,而治(conquer)的階段則將分的階段獲得的各答案"修補"在一塊兒,即分而治之)。數組
2 舉例dom
能夠看到這種結構很像一棵徹底二叉樹,本文的歸併排序咱們採用遞歸去實現(也可採用迭代的方式去實現)。分階段能夠理解爲就是遞歸拆分子序列的過程,遞歸深度爲log2n。ide
合併相鄰有序子序列函數
再來看看治階段,咱們須要將兩個已經有序的子序列合併成一個有序序列,好比上圖中的最後一次合併,要將[4,5,7,8]和[1,2,3,6]兩個已經有序的子序列,合併爲最終序列[1,2,3,4,5,6,7,8],來看下實現步驟。測試
歸併排序動態演示spa
3 代碼實現code
1 public static void main(String[] args) { 2 int[] arr = { 8, 4, 5, 7, 1, 3, 6, 2 }; 3 int[] temp = new int[arr.length];// 歸併排序須要一個額外的空間 4 mergeSort(arr, 0, arr.length - 1, temp); 5 System.out.println("歸併排序後的結果" + Arrays.toString(arr)); 6 } 7 8 /** 9 * 經過遞歸實現數組的分階段 而後調用數組合並方法 10 * 從而實現歸併排序的分而治之思想 11 * @param arr 須要排序的數組 12 * @param left 排序數組的開始索引 13 * @param right 排序數組的結束索引 14 * @param temp 輔助數組,用於接受排序的結果 15 * void 16 */ 17 public static void mergeSort(int[] arr, int left, int right, int[] temp) { 18 if (left < right) { 19 int mid = (left + right) / 2;// 中間索引 20 // 向左遞歸進行分解 21 mergeSort(arr, left, mid, temp); 22 // 向右遞歸進行分解 23 mergeSort(arr, mid + 1, right, temp); 24 // 合併 25 merge(arr, left, mid, right, temp); 26 } 27 } 28 29 /** 30 * 治階段: 將分開的2個數組合並s 31 * 32 * @param arr 排序的原始數組 33 * @param left 左邊有序數組 34 * @param mid 中間索引 35 * @param right 右邊索引 36 * @param temp 中轉的數組 37 */ 38 public static void merge(int[] arr, int left, int mid, int right, int[] temp) { 39 int i = left;// 左邊有序序列的初始索引 40 int j = mid + 1;// 右邊有序序列的初始索引 41 int t = 0; // 指向temp數組的當前索引 42 43 // 步驟一: 44 // 先把左右兩邊的數據按照規則填充到temp數組 45 // 直接左右兩邊的數組 有一邊處理完畢爲止 46 while (i <= mid && j <= right) { 47 // 若是左邊的有序序列的當前元素 小於等於右邊有序序列的當前元素 48 // 把左邊的元素添加到temp數組 49 if (arr[i] <= arr[j]) { 50 temp[t] = arr[i]; 51 i++; 52 t++; 53 } else { 54 temp[t] = arr[j]; 55 j++; 56 t++; 57 } 58 } 59 60 // 步驟二: 61 // 把有剩餘的數組一邊的數據依次填充到temp 62 while (i <= mid) { 63 temp[t] = arr[i]; 64 t++; 65 i++; 66 } 67 while (j <= right) { 68 temp[t] = arr[j]; 69 t++; 70 j++; 71 } 72 73 // 步驟三: 74 // 把temp拷貝到arr 75 //初始化t 76 t = 0; 77 int tempLeft = left; 78 // 注意 並非每次都拷貝全部 由於不須要拷貝temp 只須要拷貝當前的排好序的數組 79 while (tempLeft <= right) { 80 arr[tempLeft] = temp[t]; 81 t++; 82 tempLeft++; 83 } 84 }
4 時間複雜度orm
假如n個元素使用歸併排序的時間複雜度爲T(n),那麼因爲歸併排序使用的是分治思想,T(n)=2*T(n/2)+n,其中n就是兩個子區間合併的時間複雜度,這個從合併函數能夠看出來。能夠推導出如下公式:blog
T(1) = C; n=1 時,只須要常量級的執行時間,因此表示爲 C。
T(n) = 2*T(n/2) + n; n>1
通過進一步推導,能夠獲得T(n)=2^k * T(n/2^k) + k * n,咱們假設T(1)=T(n/2^k),也就是說當n/2^k個元素的進行歸併排序,達到遞歸終止條件時,n/2^k=1,獲得:k=logn
因而:
T(n)=Cn+nlog2n
歸併排序的時間複雜度就是O(nlogn),跟數組的有序度其實並無什麼關係,是很是穩定的時間複雜度。
5 時間複雜度速度測試
1 // 歸併排序 2 public static void main(String[] args) { 3 speedTest(8000000); 4 } 5 6 /** 7 * 建立一個隨機數組 而後調用排序方法 獲得時間 8 * 9 * @param number 建立的隨機數組個數 10 */ 11 public static void speedTest(int number) { 12 int[] arr = new int[number]; 13 int[] arr2 = new int[number]; 14 for (int i = 0; i < arr.length; i++) { 15 arr[i] = (int) (Math.random() * 800000); 16 } 17 18 SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 19 Date date1 = new Date(); 20 String time1 = simpleDateFormat.format(date1); 21 System.out.println("排序前的時間爲:" + time1); 22 23 // 調用上面的歸併排序方法 24 mergeSort(arr, 0, arr.length-1, arr2); 25 26 Date date2 = new Date(); 27 String time2 = simpleDateFormat.format(date2); 28 System.out.println("排序後的時間爲:" + time2); 29 }
歸併排序速度測試結果
8萬個數據測試結果大約須要不到1秒
80萬個數據測試結果大約須要不到
800萬個數據測試結果大約須要2秒
8000萬個數據測試結果大約16秒