歸併排序比較適合大規模得數據排序,借鑑了分治思想。數組
自古以來,分久必合合久必分。ide
咱們能夠這樣理解歸併排序,分-分到不能分爲止,而後合併。函數
使用遞歸將問題一點一點分解,最後進行合併。性能
提到遞推,咱們使用地遞推解決問題,首先要分析出遞推公式、明確結束條件。spa
遞推公式: merge_sort(i...n)=merge( merge_sort(i...j), merge_sort(j+1...n) ) 結束條件: i>=n
將兩個有序的數組進行合併,這樣整個數組也就是排序好的數組了。code
那麼怎麼進行合併呢?-- (i...j) 和 (j+1...n) 從新排序後,從新放入原來的數組 (i...n) blog
兩組數組 [3, 8, 9, 11] vs [1, 2, 5, 7]排序
兩個遊標 藍色 和 紅色遞歸
3>1,1小,1入新數組,紅色遊標後移一位,繼續比較...內存
3>2,2小,2入數組,紅色遊標後移一位
3<5,3小,3入數組,藍色遊標後移一位
8>5,5小,5入數組,紅色遊標後移一位
8>7,7小,7入數組,紅色遊標後移,右側數組所有轉移完畢
當有一組數組所有轉移完畢,那麼剩下的一組中的所有元素依次轉入到新數組中,新數組正式成爲一個有順序的數組
經過以上兩點:遞推公式和合並思想,咱們使用代碼實現一下:
一、以下圖:遞歸方式 進行分解,而後使用合併代碼進行合併。
1 /// <summary> 2 /// 遞歸調用 3 /// </summary> 4 /// <param name="a">原始數組</param> 5 /// <param name="p">分割點</param> 6 /// <param name="r">結束位置</param> 7 public static void MergrSortInternally(int[] a, int p, int r) 8 { 9 //結束條件 10 if (p >= r) 11 return; 12 13 //切割點 14 int q = p + (r - p) / 2; 15 16 //分而治之 17 MergrSortInternally(a, p, q); 18 19 MergrSortInternally(a, q + 1, r); 20 21 //合併 A(a, p, q) 和 A(a, q + 1, r) 22 Merage(a, p, q, r); 23 24 }
二、咱們再來看看合併邏輯
參數:原始數組,開始的地方,切割的地方,結束的地方
邏輯:兩個切割數組的各自的遊標
申請一樣大小的臨時數組
循環比較;小的入臨時,遊標後移;知道有一個數組空了爲止
找到剩下不爲空的那個數組,將剩餘元素入臨時
將臨時數組,找到原始數組的對應爲止進行覆蓋
1 /// <summary> 2 /// 合併 3 /// </summary> 4 /// <param name="a">原始數組</param> 5 /// <param name="p">起始點</param> 6 /// <param name="q">切割點</param> 7 /// <param name="r">結束點</param> 8 public static void Merage(int[] a, int p, int q, int r) 9 { 10 // i 和 j = 兩個數組的遊標 11 int i = p; 12 int j = q + 1; 13 14 // 臨時數組的遊標 15 int k = 0; 16 17 // 臨時數組 18 int[] temp = new int[r - p + 1]; 19 20 //最小入隊,直到其中一個空空如也爲止 21 while (i <= q && j <= r) 22 { 23 if (a[i] <= a[j]) 24 { 25 temp[k] = a[i]; 26 ++k; 27 ++i; 28 } 29 else 30 { 31 temp[k] = a[j]; 32 ++k; 33 ++j; 34 } 35 } 36 37 // 找到另外一個不爲空的,找到剩下的元素 38 int start = i; 39 int end = q; 40 41 if (j <= r) 42 { 43 start = j; 44 end = r; 45 } 46 47 // 剩餘數組拷貝到臨時數組 temp 48 while (start <= end) 49 { 50 temp[k++] = a[start++]; 51 } 52 53 // 將temp覆蓋到a[p...r] 54 for (i = 0; i <= r - p; ++i) 55 { 56 a[p + i] = temp[i]; 57 } 58 }
Q:是否是穩定排序?
A:是
對於這兩組數組 A[p...q] 和 A[q+1...r] 來講
代碼中也是這樣實現的,a[i]就是左側數組,a[j]就是右側數組,保證相等時左側優先入隊便可。注意 等號位置。
Q:是不是原地排序?
A:固然不是
由於咱們在合併代碼時候,申請了一樣大小的內存空間。
可是對於這裏的歸併排序的空間複雜度又是多少呢?
雖然牽扯到了遞歸,可是臨時變量這裏會在一個函數結束後棧會釋放,因此空間複雜度是O(n)
Q:時間複雜度又是多少呢?
A:O(n log n)
咱們對 n 個元素的歸併排序時間記做 T(n),
分解函數分解兩個子數組的時間是T(n/2)
合併函數時間複雜度是O(n)
T(1)=C; n=1
T(n)=2*T(n/2)+ n; n>1
T(n) = 2*T(n/2) + n
= 2*(2*T(n/4) + n/2) + n = 4*T(n/4) + 2*n
= 4*(2*T(n/8) + n/4) + 2*n = 8*T(n/8) + 3*n
= 8*(2*T(n/16) + n/8) + 3*n = 16*T(n/16) + 4*n
......
= 2^k * T(n/2^k) + k * n
T(n) = 2^k * T(n/2^k) + k * n
當 T(n/2^k) = T(1)=> k = log2 n
即:T(n) = Cn + n log2 n => O(n log n)