zh-hant/docs/chapter_divide_and_conquer/divide_and_conquer.md
<u>分治(divide and conquer)</u>,全稱分而治之,是一種非常重要且常見的演算法策略。分治通常基於遞迴實現,包括“分”和“治”兩個步驟。
如下圖所示,“合併排序”是分治策略的典型應用之一。
一個問題是否適合使用分治解決,通常可以參考以下幾個判斷依據。
顯然,合併排序滿足以上三個判斷依據。
分治不僅可以有效地解決演算法問題,往往還可以提升演算法效率。在排序演算法中,快速排序、合併排序、堆積排序相較於選擇、冒泡、插入排序更快,就是因為它們應用了分治策略。
那麼,我們不禁發問:為什麼分治可以提升演算法效率,其底層邏輯是什麼?換句話說,將大問題分解為多個子問題、解決子問題、將子問題的解合併為原問題的解,這幾步的效率為什麼比直接解決原問題的效率更高?這個問題可以從操作數量和平行計算兩方面來討論。
以“泡沫排序”為例,其處理一個長度為 $n$ 的陣列需要 $O(n^2)$ 時間。假設我們按照下圖所示的方式,將陣列從中點處分為兩個子陣列,則劃分需要 $O(n)$ 時間,排序每個子陣列需要 $O((n / 2)^2)$ 時間,合併兩個子陣列需要 $O(n)$ 時間,總體時間複雜度為:
$$ O(n + (\frac{n}{2})^2 \times 2 + n) = O(\frac{n^2}{2} + 2n) $$
接下來,我們計算以下不等式,其左邊和右邊分別為劃分前和劃分後的操作總數:
$$ \begin{aligned} n^2 & > \frac{n^2}{2} + 2n \newline n^2 - \frac{n^2}{2} - 2n & > 0 \newline n(n - 4) & > 0 \end{aligned} $$
這意味著當 $n > 4$ 時,劃分後的操作數量更少,排序效率應該更高。請注意,劃分後的時間複雜度仍然是平方階 $O(n^2)$ ,只是複雜度中的常數項變小了。
進一步想,如果我們把子陣列不斷地再從中點處劃分為兩個子陣列,直至子陣列只剩一個元素時停止劃分呢?這種思路實際上就是“合併排序”,時間複雜度為 $O(n \log n)$ 。
再思考,如果我們多設定幾個劃分點,將原陣列平均劃分為 $k$ 個子陣列呢?這種情況與“桶排序”非常類似,它非常適合排序海量資料,理論上時間複雜度可以達到 $O(n + k)$ 。
我們知道,分治生成的子問題是相互獨立的,因此通常可以並行解決。也就是說,分治不僅可以降低演算法的時間複雜度,還有利於作業系統的並行最佳化。
並行最佳化在多核或多處理器的環境中尤其有效,因為系統可以同時處理多個子問題,更加充分地利用計算資源,從而顯著減少總體的執行時間。
比如在下圖所示的“桶排序”中,我們將海量的資料平均分配到各個桶中,則可將所有桶的排序任務分散到各個計算單元,完成後再合併結果。
一方面,分治可以用來解決許多經典演算法問題。
另一方面,分治在演算法和資料結構的設計中應用得非常廣泛。
可以看出,分治是一種“潤物細無聲”的演算法思想,隱含在各種演算法與資料結構之中。