分治策略及其應用

一、分治策略簡介

在很多情況下,分治策略有可能是簡單而強有力的方法,可以用來提高算法的效率

不要問我爲什麼,他就是這樣的……

大概的步驟有三部分

1,將輸入的數據分成若干部分

2、遞歸的求解每個部分的問題

3、將子問題的解組合成爲一個全局的解

二、應用領域

分治算法可以被運用到許許多多的方面,可以充當其他算法的一個i組成部分,也可以稍做修改成爲另一個算法,例如

歸併排序

找平面上最鄰近的點對問題

降低整數相乘的複雜度

消除信號的噪音

……

三、算法核心部分分析

馬克思和恩格斯教導我們一定要抓住主要矛盾的主要方面

從我的角度看,我們學習分治算法的主要矛盾是將其應用的其他方面

而主要方面就是一定要理解算法的核心部分

這樣就可以根據實際情況對分治算法做出適當的修改,提高解決問題的效率

接下來我將會通過解決一下幾個問題,讓大家比較簡單的掌握分治算法

  • 分治算法的整體步驟大概是什麼樣的

  • 怎麼預計和分析一個分治算法的好壞,即對分治算法的複雜度進行評估

  • 怎麼改進一個分治算法

上面以及提到過分治算法的大概過程

我再重述一下

  1. 將輸入的數據分成若干部分

  2. 遞歸的求解每個部分的問題

  3. 將子問題的解組合成爲一個全局的解

爲了讓大家更好的理解這個過程就拿最爲熟悉的歸併排序算法舉例


嘛也不說直接上源代碼

python實現

# 歸併排序主體
def mergeSort(arr):
    # 如果數組中只有兩個數
    if(len(arr)<2):
        return arr
    
    middle = math.floor(len(arr)/2)
    
    # 將現有的整個數組分成左右兩個部分,採用的切割方法是從middle處切割
    left, right = arr[0:middle], arr[middle:]
    # 最後遞歸合併
    return merge(mergeSort(left), mergeSort(right))

def merge(left,right):
    # 存儲結果
    result = []
    
    # 比較排頭的元素
    while left and right:
        if left[0] <= right[0]:
            result.append(left.pop(0))
        else:
            result.append(right.pop(0))
    # 合併左右部分
    while left:
        result.append(left.pop(0))
    while right:
        result.append(right.pop(0))
    
    return result

if __name__ == '__main__':
    list = [1, 5, 8, 45, 98, 90, 78, 99, 123, 2]
    print("List is:", list)
    result = mergeSort(list)
    print("list sorted :", result)

相信很多人又這樣的問題,爲啥這玩意要在middle處切割的,又爲啥要分成兩個部分,不分成三個,四個……部分呢?這些問題就是我們下面要詳細討論的了


“小朋友,你是否有很多問好???”


說實話,一開始學的時候我也一臉懵逼━┳━ ━┳━

要想弄懂這個問題就我們就需要知道下面這個公式,並且需要會進行對應的推導


四、公式推導

首先定義T(n)爲該算法在規模爲n的輸入實例的最壞的運行時間

然後看

T(n)={aT(n/b)+g(n)(n>1)0 (n=1) T\left( n \right) =\left\{ \begin{array}{l} aT\left( n/b \right) +g\left( n \right) \left( n>1 \right)\\ 0\ \left( n=1 \right)\\ \end{array} \right.

這個公式

這個公式就是歸併排序的遞推公式,下面的很多問題就是圍繞這個問題展開的


公式的含義

  • 一個規模爲n的問題每一次被分成了a個子問題,

  • 每個問題的規模是n/b

  • 各個子問題的解的複雜度爲 g(n)


接下來的這個圖你們一定就可以看懂了

在這裏插入圖片描述

這個圖就是上面歸併排序的分治策略圖


n爲2的倍數

接下來我們就手動推導出歸併排序的複雜度爲T(n)=nlog2nT\left( n \right) = n\log_{2}n

首先根據

T(n)=aT(nb)+g(n)T\left( n \right) = aT\left( \frac{n}{b} \right) + g\left( n \right)

我們可以寫出

歸併排序中每一次被分爲2個子問題,每一個子問題的規模是其爸爸的一半,因爲歸併排序進行排序操作的時候只比較頭兩個元素的大小,當一半爲空時,直接把另一半的其他部分加到結果上就行,所以每一個子問題的複雜度爲n

首先從簡單的n爲2的倍數開始

T(n)=2T(n2)+nT\left( n \right) = 2T\left( \frac{n}{2} \right) + n

T(n)n=2T(n2)n+1\frac{T\left( n \right)}{n} = \frac{2T\left( \frac{n}{2} \right)}{n} + 1

=T(n2)n2+1= \frac{T\left( \frac{n}{2} \right)}{\frac{n}{2}} + 1

=T(n4)n4+1+1= \frac{T\left( \frac{n}{4} \right)}{\frac{n}{4}} + 1 + 1

=T(n8)(n8)+1+1+1= \frac{T\left( \frac{n}{8} \right)}{\left( \frac{n}{8} \right)} + 1 + 1 + 1

\ldots\ldots

=T(nn)(nn)+log2n1= \frac{T\left( \frac{n}{n} \right)}{\left( \frac{n}{n} \right)} + \log_{2}n*1

=log2n= \log_{2}n

T(n)=nlog2nT\left( n \right) = n\log_{2}n


n爲自然數

其次對於一般情況我們要證明

T(n)n ceiling(log2n)T\left( n \right) \leq n\ ceiling\left( \log_{2}n \right)

遞推公式爲
T(n)<= {0 (n=1)T(ceiling(n/2))+T(floor(n/2))+n (n>1) T\left( n \right) <=\ \left\{ \begin{array}{l} 0\ \left( n=1 \right)\\ T\left( ceiling\left( n/2 \right) \right) +T\left( floor\left( n/2 \right) \right) +n\ \left( n>1 \right)\\ \end{array} \right.
我們採用歸納法

當n= 1時顯然成立

n1=floor(n2) n2=ceiling(n2)n_{1} = floor\left( \frac{n}{2} \right)\ n_{2} = \text{ceiling}\left( \frac{n}{2} \right)

假設n1n - 1命題時命題成立

T(n)<=T(n1)+T(n2)+n T\left( n \right) <=T\left( n_1 \right) +T\left( n_2 \right) +n <=n1(ceiling(log2n1))+n2(ceiling(log2 n2))+n <=n_1\left( ceiling\left( \log _2n_1 \right) \right) +n_2\left( ceiling\left( \log _2\ n_2 \right) \right) +n <=n1(ceiling(log2 n2))+n2(ceiling(log2 n2))+n <=n_1\left( ceiling\left( \log _2\ n_2 \right) \right) +n_2\left( ceiling\left( \log _2\ n_2 \right) \right) +n =n(ceiling(log2 n2))+n =n\left( ceiling\left( \log _2\ n_2 \right) \right) +n <=n(ceiling(log2 ceiling(n/2)))+n <=n\left( ceiling\left( \log _2\ ceiling\left( n/2 \right) \right) \right) +n <=n(ceiling(log2 n)1)+n <=n\left( ceiling\left( \log _2\ n \right) -1 \right) +n <=n(ceiling(log2 n)) <=n\left( ceiling\left( \log _2\ n \right) \right)

綜上,我們回答剛剛的問題爲啥這玩意要在middle處切割的,又爲啥要分成兩個部分,不分成三個,四個…部分呢?

其實可以在不同地方切割,我們一般選擇在middle出切割,也可選擇切成多個部分,但是這樣的複雜度時不同的


g(n)=c

對於g(n)=c

這個公式最後可以證明

T(n)=O(nlogba) T\left( n \right) = O\left( n^{\log_{b}a} \right)\


g(n) = c*n^d

對於g(n)=cndg\left( n \right) = \text{cn}^{d}

滿足如下定理

設函數滿足這個遞推關係T(n)=aT(nb)+cndT\left( n \right) = aT\left( \frac{n}{b} \right) + \text{cn}^{d}的增函數,其中n=bkn = b^{k},a>=1,b是大於1的正整數,c,d爲實數且c>=0,d>=0,那麼
T(n)={O(nd) a<bdO(ndlog n) a=bdO(nloga b) a>bd T\left( n \right) =\left\{ \begin{array}{l} O\left( n^d \right) \ a<b^d\\ O\left( n^d\log\text{\ }n \right) \ a=b^d\\ O\left( n^{\log _a\ b} \right) \ a>b^d\\ \end{array} \right.
有興趣的可以自己證明一下,這裏就不給出證明了

敲公式實在太累人了ε(┬┬﹏┬┬)3

五、分治算法的應用

經過對核心算法的理解

我們就可以解決之前提出的問題了

分治算法第一步就是就是將問題分成對應的a部分,每個問題的規模是n/b之後遞歸求解每一個問題,最後進行組合

那怎麼對一個分治算法的複雜度進行評估呢?

我們可以用上面T(n)公式進行推導,將對應的a,b,g(n),帶入然後計算出T(n)

同時我們可以運用不同的計算方法改變a,b,g(n)的值,由此來達到改進算法效率的目的


接下來我們就通過改進整數的乘法來看一下分治算法的牛逼之處吧

最普通的乘法少說一點吧

x*y,在計算機中是通過二進制來計算的,所以是先轉爲二進制再相乘

複雜度爲O(n^2)

接下來我們通過分治算法來進行改進


我們令

x=2(n/2)x1+x0 x = 2^{(n/2)}*x1+x0

y=2(n/2)y1+y0y = 2^{(n/2)}*y1+y0

xy=(2(n/2)x1+x0)(2(n/2)y1+y0)x*y = (2^{(n/2)}*x1+x0)(2^{(n/2)}*y1+y0)

=2nx1y1+2(n/2)(x1y0+x0y1)+x0y0= 2^n *x1* y1+2^{(n/2)}*(x1*y0+x0*y1)+x0*y0

我們可以通過遞推公式來看一下,現在的算法優劣情況

T(n)=4T(n/2)+O(n)T(n) = 4T(n/2) +O(n)

=>T(n)=O(n2)=> T(n)=O(n^2)

O(n) 爲每次移位和相加的複雜度

貌似沒有啥改變……

接下來看看我們可不可以通過改變a,b,O(n)的值來降低複雜度

可以看出O(n),沒有啥可以很好的改進的地方

但是我們可以發現

xy=2nx1y1+2(n/2)(x1y0+x0y1)+x0y0x*y=2^n *x1* y1+2^{(n/2)}*(x1*y0+x0*y1)+x0*y0

=2nx1y1+2(n/2)((x1+x0)(y1+y0)x1y1x0y0)+x0y0= 2^n *x1*y1+2^{(n/2)}*((x1+x0)*(y1+y0)-x1*y1-x0*y0)+x0*y0

這樣就成將4次乘法降低爲3次了
讓我們再來看一次複雜度

T(n)=3T(n/2)+O(n)=>T(n)=O(n(1.585))T(n) = 3T(n/2) +O(n)=>T(n)=O(n^{(1.585)})

d=====( ̄▽ ̄*)b

讓我們最後再看一下改進後的整數相乘的僞代碼

def Recursive_Multiply(x,y):
    x = x1 * 2**(n/2) + x0
    y = y1 * 2**(n/2) + y0
    # 計算x1+x0 ,y1+y0
    p = Recursive_Multiply(x1+x0,y1+y0)
    
    x1 * y1 = Recursive_Multiply(x1,y1)
    x0 * y0 = Recursive_Multiply(x0,y0)
    return x1 * y1 * 2**n + (p-x1 * y1 - x0 * y0) * 2**(n/2) + x0 * y0

小編是個菜雞,若有錯誤請大佬們不吝指正

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章