分治算法 這個博客介紹了分治的基本內容git
首先回答分治法的基本思想:在解決一個問題的時候,能夠把這個問題分紅子問題,子問題的求解方式和原問題基本相同,這樣能夠不斷劃分,直到問題可以以最小的形式解決,而後將子問題的結果合併起來就是原問題的解決方法。github
分治法的適用狀況:1:問題規模足夠小的時候可以解決。 2:該問題能夠劃分爲規模較小的若干問題。 3:子問題的解合併是原來問題的解。 4:各個子問題相互獨立。算法
分治法的執行過程:1:問題拆分。 2:子問題求解。 3:合併。數組
算法複雜性分析:複雜性是一個遞歸公式,$T(n) = aT(\frac{n}{b}) + f(n)$ b爲子問題相對於原問題的規模,a爲子問題個數,f(n)表示剩餘程序的複雜度。app
若f(n)複雜度爲$O(n^d)$,這個時候公式變爲了$T(n) = aT(\frac{n}{b}) + O(n^d)$ ,通常用下面的主方法進行分析。ide
$$ T(x)=\left\{ \begin{aligned} O(n^d) & & a< b^d\\ O(n^d \log n) & & a = b^d \\ O(n^{log_b a}) & & a>b^d \end{aligned} \right. $$函數
下面是一些分治法的例子:spa
若將一個數組排序,1:須要將其拆分爲左右兩部分 2:左右兩部分都須要進行排序 3:合併的時候使用外排的方法進行合併。設計
def merge_sort(array): """歸併排序 使用分治法的思想來進行排序, 使用遞歸的方法來進行實現 時間複雜度:使用master公式 a=2, b=2。除去遞歸,其它問題的複雜度,也就是merge的複雜度o(n), 因此,總體的複雜度爲O(n*logn) 空間複雜度:歸併排序每次遞歸須要用到一個輔助表,長度與待排序的表相等, 雖然遞歸次數是O(log2n),但每次遞歸都會釋放掉所佔的輔助空間, 因此下次遞歸的棧空間和輔助空間與這部分釋放的空間就不相關了,於是空間複雜度仍是O(n) 穩定性: 穩定性算法。在merge的時候,若是兩個值相等,能夠控制讓左邊先進來,而後右邊再進來。 """ if len(array) <= 1: return array mid = int(len(array)/2) left = merge_sort(array[:mid]) right = merge_sort(array[mid:]) return merge(left, right) def merge(left, right): """合併算法 剛開始並沒與價差兩個數組的長度,而是直接相減,直到有一個爲爲止 時間複雜度:o(n) 須要遍歷一邊left和right 空間複雜度爲 o(n) 須要一個result數組來存儲結果 """ result = [] l, r = 0, 0 while(l < len(left) and r < len(right)): if left[l] < right[r]: result.append(left[l]) l += 1 else: result.append(right[r]) r += 1 result += (left[l:]) result += (right[r:]) return result
來自算法導論4.1章,問題是這樣的,給定一組數組,求這個數組當中連續數組和的最大值,和數組的邊界。好比在數組code
[13, -3, -25,20,-3,-16,-23,18,20,-7,12,-5,-22,15,-4,7] 中最大子數組是從18到12的連續4個數,它們的和爲43。
將原來的數組劃分爲兩個數組,這樣最大子數組只可能有三種狀況:徹底位於左邊數組,徹底位於右邊數組,位於兩個數組中間,對於前兩個問題,遞歸的進行求解,對於第三個問題能夠設計單獨的函數求解。
採用分治法的方法來解決這個問題:1:能夠將一個數組分爲左右兩個部分。 2:變成三個子問題:分別求左邊部分中的最大子數組、右邊部分的最大子數組 ,還有包含中間節點的最大子數組。3:合併這三種狀況。上面劃線的分析是錯誤的,2:變爲兩個子問題,求解左邊部分的最大子數組,求解右邊部分的最大子數組,使用遞歸的方式來解。 3:考慮最大子數組的三種狀況,而後進行合併,由於求解包含中間節點的最大子數組沒有調用遞歸算法,因此不屬於子問題,而劃分到合併裏面。
def find_maximum_subarray(arr, low, high): """發現最大子數組""" if low == high: return (low, high, arr[low]) mid = int((low+high)/2) left_low, left_high, left_sum = find_maximum_subarray(arr, low, mid) # 劃分而且求解左子部分 right_low, right_high, right_sum = find_maximum_subarray(arr, mid + 1, high) # 劃分而且求解右子部分 mid_low, mid_high, mid_sum = find_max_crossing_subarray(arr, low, mid, high) # 求解出如今中間的狀況 # 合併子問題 if left_sum >= right_sum and left_sum >= mid_sum: return left_low, left_high, left_sum elif right_sum >= left_sum and right_sum >= mid_sum: return right_low, right_high, right_sum else: return mid_low, mid_high, mid_sum def find_max_crossing_subarray(arr, low, mid, high): """當數組最大值包含mid的時候的求解方法""" # 先求解low到min的最大子數組 left_sum = float('-inf') all_sum = 0 left_index = mid for index in range(mid, low-1, -1): all_sum += arr[index] if all_sum > left_sum: left_sum = all_sum left_index = index # 而後求解mid到high的最大子數組 right_sum = float('-inf') all_sum = 0 right_index = mid+1 for index in range(mid+1, high+1): all_sum += arr[index] if all_sum > right_sum: right_sum = all_sum right_index = index # 將兩部分的結果合併 return (left_index, right_index, left_sum+right_sum) def max_subarray(arr): return find_maximum_subarray(arr, 0, len(arr)-1) if __name__ == '__main__': arr = [13, -3, -25,20,-3,-16,-23,18,20,-7,12,-5,-22,15,-4,7] x = max_subarray(arr) print(x)
使用動態規劃的方式來解:
def dp_max_sub_array(arr): if len(arr) == 0: return 0 max_sub = arr[0] tmp_sub = arr[0] for i in range(1, len(arr)): tmp_sub = max(tmp_sub + arr[i], arr[i]) max_sub = max(max_sub, tmp_sub) return max_sub