分治法

分治法:

分治算法 這個博客介紹了分治的基本內容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
相關文章
相關標籤/搜索