分而治之
從算法設計的分類上來說,插入排序屬于增量方法。在排序好子數組A[1 ‥ j-1]后,再將單個元素A[j]插入子數組的適當位置,產生排序好的子數組A[1 ‥ j]。整個算法就是不斷以此方法增量插入,直到子數組包含了所有數組元素。
本篇將要介紹的歸并排序,是用另一種思想來解決排序問題的,在算法設計分類上屬于分治法。
分治法思想是,將原問題分解為幾個規模較小但類似于原問題的子問題,遞歸的求解這些子問題,然后在合并這些子問題的解,最終建立原問題的解。
這里提到一個詞遞歸,其解釋是:為了解決一個給定問題,算法一次或多次的調用其自身以解決緊密相關的子問題。遞歸是分治思想的一個具體實現。
分治模式在每層遞歸時都有三個步驟:
- 分解:將原問題分解為若干子問題,這些子問題是原問題的規模較小的實例;
- 解決:遞歸的求解各子問題;
- 合并:合并子問題的解,得到原問題的解。
看到這里,“直覺”上可能會產生一個極大的疑問:最底層的子問題是在哪里解決的?產生這個疑問是正常的,因為第二步“解決”也僅僅是調用自身,其實就是重新進入了下一層的分解、解決和合并,而沒有看到“如何解決”。
答案是:無需解決。換句話說,層層分解到子問題的規模足夠小時,解就自己出現了。后面還會再提到這一點。
歸并排序偽碼
歸并排序按照分治法的三個步驟如下:
- 分解:分解待排序的n個元素的序列,變成各具n/2個元素的兩個子序列;
- 解決:遞歸的調用自身排序兩個子序列;
- 合并:合并兩個已排序的子序列以產生最終排序的序列。
上一篇合并算法中已經解決了合并算法MERGE,歸并排序就剩下如何進行分解,和遞歸調用了。
看代碼的確就這三步:
MERGE-SORT(A, p, r)
1 if p < r
2 q = (p + r) / 2
3 MERGE-SORT(A, p, q)
4 MERGE-SORT(A, q+1, r)
5 MERGE(A, p, q, r)
注:(p + r) / 2如果不是整除,則取小于它的最大整數。
p < r時,表明數組有繼續拆分的可能。當p ≥ r時,則表示該子數組最多有一個元素,所以無需排序就已經是排好序了,這就是分解到足夠小會導致的自動解決。換句話說,我們一直把數組分解下去,直到分成每個子數組只包含1個元素時,即第3行中p = q,第4行中q+1 = r,那么第3和第4行的MERGE-SORT會立即返回,并執行MERGE,然后返回上一層MERGE-SORT,直到最上層。
一個例子
一個有8個元素的數組A[5, 2, 4, 7, 1, 3, 2, 6],采用歸并排序的圖示如下圖。圖中的下方藍區部分是上面白區的數組不同時刻的鏡像。白區主要在做“分解”,藍區主要在做“合并”。
歸并排序Java代碼
public static void mergeSortInASC(int [] numbers, int p, int r) throws Exception {
if(p < r){
int q = (int)Math.floor((p + r) / 2);
mergeSortInASC(numbers, p, q);
mergeSortInASC(numbers, q + 1, r);
mergeInASC(numbers, p, q, r);
}
}
共享協議:署名-非商業性使用-禁止演繹(CC BY-NC-ND 3.0 CN)
轉載請注明:作者黑猿大叔(簡書)