分而治之(divide and conquer,D&C)是一種著名的遞歸式問題解決方法。
只能解決一種問題的算法畢竟用處有限,而D&C提供瞭解決問題的思路,是另外一個可供你使用的工具。python
D&C算法是遞歸的。使用D&C解決問題的過程包括兩個步驟。
(1) 找出基線條件,這種條件必須儘量簡單。
(2) 不斷將問題分解(或者說縮小規模),直到符合基線條件。算法
假設你是農場主,有一小塊土地。如何將一塊地均勻地分紅方塊,並確保分出的方塊是最大的呢?
最容易處理的狀況是,一條邊的長度是另外一條邊的整數倍。好比,25m x 50m的土地能夠分紅 2 個 25m x 25m 的方塊。數組
根據D&C的定義,每次遞歸調用都必須縮小問題的規模。首先找出這塊地可容納的最大方塊。如,上圖能夠劃出兩個640 m × 640 m的方塊,同時餘下一小塊400m x 640m 地。
咱們下一步要作的就是對餘下的那一小塊地使用相同的算法。由於適用於這小塊地的最大方塊,也是適用於整塊地的最大方塊。換言之,你將均勻劃分1680 m × 640 m土地的問題,簡化成了均勻劃分400m x 640 m土地的問題!less
咱們很容易實現上述過程。咱們進一步抽象,這個過程實際上就是求兩個整數的最大公倍數。ide
給定一個數字數組,如,[2,4,6],怎麼返回這些數字相加後的結果。使用循環能夠很容易實現。那使用遞歸怎麼實現呢?
最簡單的數組不包含任何元素或只包含一個元素,這個能夠認爲是數組的基線條件。函數
每次遞歸調用都必須離空數組更近一步。咱們經過下面的等式縮小問題的規模。sum [2,4,6] == 2 + sum [4,6]
工具
使用Haskell能夠很容易實現:ui
sum [] = 0 sum (x:xs) = x + (sum xs)
快速排序是一種經常使用的排序算法,如,C語言標準庫中的函數qsort
實現的就是快速排序。spa
數組爲空或只包含一個元素。在這種狀況下,只需原樣返回數組。code
咱們從數組中選擇一個元素做爲基準值(pivot),而後以該值爲基準對數據分區(partitioning),這樣數組劃分紅了三部分:
這樣問題縮小到了子數組規模。再分別對子數組應用以上過程,獲得排序後的子數組,最終咱們只要將這三部分拼接起來就能獲得徹底排序的數組。
注意:爲了實現簡單,基準值每次都取的數組首元素。
代碼以下:
# python def quicksort(array): if len(array) < 2: return array else: pivot = array[0] less = [i for i in array[1:] if i <= pivot] greater = [i for i in array[1:] if i > pivot] return quicksort(less) + [pivot] + quicksort(greater)
--haskell import Data.List quickSort :: Ord a => [a] -> [a] quickSort [] = [] quickSort (x:xs) = quickSort lhs ++ [x] ++ quickSort rhs where (lhs, rhs) = partition (< x) xs
注意:上面的版本每次都新生成子數組,有些人認爲正確的快速排序應該使用in-place交換,因此上面的算法不「正宗」。
快速排序的獨特之處在於,其速度取決於選擇的基準值。在平均狀況下,快速排序的運行時間爲O(nlog n),在最糟狀況下,退化爲O(n2)。還有一種合併排序(merge sort)的排序算法,其運行時間爲O(nlogn)。
大O表示法體現出的是對元素規模n的增速,但處理每一個元素的速度是有差別的,好比,對每一個元素執行(*2)
和(+3).(*2)操做,明顯是後者執行的時間長。快速排序和合並排序的算法速度分別表示爲c1 nlogn和c2 nlogn,c是算法所需的固定時間量,被稱爲常量。一般不考慮這個常量,由於若是兩種算法的大O運行時間不一樣,這種常量將可有可無。但有時候,常量的影響可能很大,對快速查找和合並查找來講就是如此。快速查找的常量比合並查找小,所以若是它們的運行時間都爲O(n log n),快速查找的速度將更快。實際上,快速查找的速度確實更快,由於相對於趕上最糟狀況,它趕上平均狀況的可能性要大得多。