動態規劃Dynamic Programming

1.動態規劃理論

1.1動態規劃基本思想

 

 

注意:斐波那契遞歸求解的時間複雜度爲O(2n)。python

 

子問題不獨立適合動態規劃算法設計。git

分治:將原問題劃分爲互不相交的子問題,遞歸求解子問題,再將它們的解組合起來。算法

動態規劃:子問題重疊的狀況,不一樣的子問題具備公共的子子問題數組

 

 

 

利用動態規劃須要知足:框架

  1. 問題中的狀態知足最優性原理。 =》最優子結構
  2. 問題中的狀態必須知足無後效性。 =》之前出現的狀態和之前狀態的變化過程不會影響未來的變化

 

1.2動態規劃的基本步驟

 

 2.動態規劃例子-矩陣相乘

問題提出:

關鍵計算問題:

徹底加括號的矩陣連乘積:

 

 

3.舉例

1.任務調度安排

                                              

 

 

 

。。。學習

 

 

 

2. 不相鄰數字和最大

求一堆不相鄰數字和最大,沒有要求選兩個,能夠選多個。優化

 

OPT(n)指的是數據到下標爲n的數字選取的最好的方案。網站

 

 

#遞歸方程實現
def rec_opt(arr, i):
    if i == 0:
        return arr[0]
    elif i == 1:
        return max(arr[0], arr[1])
    else:
        A = rec_opt(arr, i - 2) + arr[i]#選擇當前值
        B = rec_opt(arr, i - 1)#不選當前值
        return max(A, B)

#非遞歸實現:dp動態規劃
def dp_opt(arr):
    opt = [0 for i in range(len(arr))]
    opt[0] = arr[0]
    opt[1] = max(arr[0], arr[1])
    for i in range(2,len(arr)):
        A = opt[i-2] + arr[i]
        B = opt[i-1]
        opt[i] = max(A,B)
    return opt[-1]

arr1 = [4, 1, 1, 9, 1]
arr2 = [1, 2, 4, 1, 7, 8, 3]
print(rec_opt(arr1, len(arr1)-1))#13
print(dp_opt(arr1))#13
print(rec_opt(arr2, len(arr2)-1))#15
print(dp_opt(arr2))#15

 

3.給出一個數字和一個數組,判斷是否數組中存在一些數的和爲給出的數字

 

#遞歸實現
def rec_subset(arr, i, s):
    if s == 0:
        return True
    elif i == 0:
        return arr[0] == s
    elif arr[i] > s:
        return rec_subset(arr, i-1, s)
    else:
        A = rec_subset(arr, i-1, s-arr[i])
        B = rec_subset(arr, i-1, s)
        return A or B

arr = [3, 34, 4, 12, 5, 2]
print(rec_subset(arr, len(arr)-1, 9))#True
print(rec_subset(arr, len(arr)-1, 10))#True
print(rec_subset(arr, len(arr)-1, 13))#False

 

 

#非遞歸實現:dq動態規劃
def dp_subset(arr, S):
    import numpy as np
    subset = np.zeros((len(arr), S+1), dtype=bool)
    subset[:, 0] = True
    subset[0, :] = False
    subset[0, arr[0]] = True
    for i in range(1, len(arr)):
        for s in range(1, S+1):
            if arr[i] > s:
                subset[i, s] = subset[i-1, s]
            else:
                A = subset[i-1, s-arr[i]]
                B = subset[i-1, s]
                subset[i, s] = A or B
    r, c = subset.shape
    return subset[r-1, c-1]
arr = [3, 34, 4, 12, 5, 2]
print(dp_subset(arr, 9))#True
print(dp_subset(arr, 10))#True
print(dp_subset(arr, 13))#False

 

系統學習

1 揹包問題

  假設你是個小偷,揹着一個可裝4磅東西的揹包。你可盜竊的商品有以下3件。spa

  爲了讓盜竊的商品價值最高,你該選擇哪些商品?設計

1.1 簡單算法

  最簡單的算法以下:嘗試各類可能的商品組合,並找出價值最高的組合。

  這樣可行,但速度很是慢。在有3件商品的狀況下,你須要計算8個不一樣的集合;有4件商品時,你須要計算16個集合。每增長一件商品,須要計算的集合數都將翻倍!這種算法的運行時間爲O(2n),真的是慢如蝸牛。

  只要商品數量多到必定程度,這種算法就行不通。在第8章,你學習瞭如何找到近似解,這接近最優解,但可能不是最優解。那麼如何找到最優解呢?

1.2 動態規劃

  答案是使用動態規劃!下面來看看動態規劃算法的工做原理。動態規劃先解決子問題,再逐步解決大問題。

  對於揹包問題,你先解決小揹包(子揹包)問題,再逐步解決原來的問題。

  動態規劃是一個難以理解的概念,若是你沒有當即搞懂,也不用擔憂,咱們將研究不少示例。

  先來演示這種算法的執行過程。看過執行過程後,你內心將有一大堆問題!我將竭盡所能解答這些問題。

  每一個動態規劃算法都從一個網格開始,揹包問題的網格以下。

  網格的各行爲商品,各列爲不一樣容量(1~4磅)的揹包。全部這些列你都須要,由於它們將幫助你計算子揹包的價值。

  網格最初是空的。你將填充其中的每一個單元格,網格填滿後,就找到了問題的答案!你必定要跟着作。請你建立網格,咱們一塊兒來填滿它。

  1. 吉他行
  後面將列出計算這個網格中單元格值的公式。咱們先來一步一步作。首先來看第一行。

  這是吉他行,意味着你將嘗試將吉他裝入揹包。在每一個單元格,都須要作一個簡單的決定:偷不偷吉他?別忘了,你要找出一個價值最高的商品集合。

  第一個單元格表示揹包的容量爲1磅。吉他的重量也是1磅,這意味着它能裝入揹包!所以這個單元格包含吉他,價值爲1500美圓。

  下面來開始填充網格。

  與這個單元格同樣,每一個單元格都將包含當前可裝入揹包的全部商品。

  來看下一個單元格。這個單元格表示揹包的容量爲2磅,徹底可以裝下吉他!

  這行的其餘單元格也同樣。別忘了,這是第一行,只有吉他可供你選擇。換言之,你僞裝現在還無法盜竊其餘兩件商品。

  此時你極可能心存疑惑:原來的問題說的是4磅的揹包,咱們爲什麼要考慮容量爲1磅、2磅等的揹包呢?前面說過,動態規劃從小問題着手,逐步解決大問題。這裏解決的子問題將幫助你解決大問題。請接着往下讀,稍後你就會明白的。

  此時網格應相似於下面這樣。

  別忘了,你要作的是讓揹包中商品的價值最大。這行表示的是當前的最大價值。它指出,若是你有一個容量4磅的揹包,可在其中裝入的商品的最大價值爲1500美圓。

  2. 音響行
  咱們來填充下一行——音響行。你如今出於第二行,可偷的商品有吉他和音響。在每一行,可偷的商品都爲當前行的商品以及以前各行的商品。所以,當前你還不能偷筆記本電腦,而只能偷音響和吉他。咱們先來看第一個單元格,它表示容量爲1磅的揹包。在此以前,可裝入1磅揹包的商品的最大價值爲1500美圓。

  該不應偷音響呢?
  揹包的容量爲1磅,能裝下音響嗎?音響過重了,裝不下!因爲容量1磅的揹包裝不下音響,所以最大價值依然是1500美圓。

  接下來的兩個單元格的狀況與此相同。在這些單元格中,揹包的容量分別爲2磅和3磅,而之前的最大價值爲1500美圓。

  因爲這些揹包裝不下音響,所以最大價值保持不變。
  揹包容量爲4磅呢?終於可以裝下音響了!原來的最大價值爲1500美圓,但若是在揹包中裝入音響而不是吉他,價值將爲3000美圓!所以仍是偷音響吧。

  你更新了最大價值!若是揹包的容量爲4磅,就能裝入價值至少3000美圓的商品。在這個網格中,你逐步地更新最大價值。

  3. 筆記本電腦行
  下面以一樣的方式處理筆記本電腦。筆記本電腦重3磅,無法將其裝入容量爲1磅或2磅的揹包,所以前兩個單元格的最大價值仍是1500美圓。

  對於容量爲3磅的揹包,原來的最大價值爲1500美圓,但如今你可選擇盜竊價值2000美圓的筆記本電腦而不是吉他,這樣新的最大價值將爲2000美圓!

  對於容量爲4磅的揹包,狀況頗有趣。這是很是重要的部分。當前的最大價值爲3000美圓,你可不偷音響,而偷筆記本電腦,但它只值2000美圓。

  價值沒有原來高。但等一等,筆記本電腦的重量只有3磅,揹包還有1磅的容量沒用!

  在1磅的容量中,可裝入的商品的最大價值是多少呢?你以前計算過。

  根據以前計算的最大價值可知,在1磅的容量中可裝入吉他,價值1500美圓。所以,你須要作以下比較。

  你可能始終心存疑惑:爲什麼計算小揹包可裝入的商品的最大價值呢?希望你如今明白了其中的緣由!餘下了空間時,你可根據這些子問題的答案來肯定餘下的空間可裝入哪些商品。筆記本電腦和吉他的總價值爲3500美圓,所以偷它們是更好的選擇。最終的網格相似於下面這樣。

  答案以下:將吉他和筆記本電腦裝入揹包時價值最高,爲3500美圓。
  你可能認爲,計算最後一個單元格的價值時,我使用了不一樣的公式。那是由於填充以前的單元格時,我故意避開了一些複雜的因素。其實,計算每一個單元格的價值時,使用的公式都相同。這個公式以下。

  你可使用這個公式來計算每一個單元格的價值,最終的網格將與前一個網格相同。如今你明白了爲什麼要求解子問題吧?你能夠合併兩個子問題的解來獲得更大問題的解。

 2 揹包問題 FAQ

  你可能仍是以爲這像是變魔術。

2.1 再增長一件商品將如何呢

  假設你發現還有第四件商品可偷——一個iPhone!

  此時須要從新執行前面所作的計算嗎?不須要。別忘了,動態規劃逐步計算最大價值。到目前爲止,計算出的最大價值以下。

  這意味着揹包容量爲4磅時,你最多可偷價值3500美圓的商品。但這是之前的狀況,下面再添加表示iPhone的行。  最大價值可能發生變化!請嘗試填充這個新增的行,再接着往下讀。
  咱們從第一個單元格開始。iPhone可裝入容量爲1磅的揹包。以前的最大價值爲1500美圓,但iPhone價值2000美圓,所以該偷iPhone而不是吉他。

  在下一個單元格中,你可裝入iPhone和吉他。

  對於第三個單元格,也沒有比裝入iPhone和吉他更好的選擇了。

  對於最後一個單元格,狀況比較有趣。當前的最大價值爲3500美圓,但你可偷iPhone,這將
餘下3磅的容量。
  3磅容量的最大價值爲2000美圓!再加上iPhone價值2000美圓,總價值爲4000美圓。新的最
大價值誕生了!
  最終的網格以下。

  問題:沿着一列往下走時,最大價值有可能下降嗎?

  答案:不可能。每次迭代時,你都存儲當前的最大價值。最大價值不可能比之前低!

 

2.2 行的排列順序發生變化時結果將如何

  答案會隨之變化嗎?假設你按以下順序填充各行:音響、筆記本電腦、吉他。網格將會是什麼樣的?請本身動手填充這個網格,再接着往下讀。
  網格將相似於下面這樣。

  答案沒有變化。也就是說,各行的排列順序可有可無。

 

2.3 能夠逐列而不是逐行填充網格嗎

  本身動手試試吧!就這個問題而言,這沒有任何影響,但對於其餘問題,可能有影響。

 

2.4 增長一件更小的商品將如何呢

  假設你還能夠偷一條項鍊,它重0.5磅,價值1000美圓。前面的網格都假設全部商品的重量爲整數,但如今你決定把項鍊給偷了,所以餘下的容量爲3.5磅。在3.5磅的容量中,可裝入的商品的最大價值是多少呢?不知道!由於你只計算了容量爲1磅、2磅、3磅和4磅的揹包可裝下的商品的最大價值。如今,你須要知道容量爲3.5磅的揹包可裝下的商品的最大價值。
  因爲項鍊的加入,你須要考慮的粒度更細,所以必須調整網格。


2.5 能夠偷商品的一部分嗎

  假設你在雜貨店行竊,可偷成袋的扁豆和大米,但若是整袋裝不下,可打開包裝,再將揹包倒滿。在這種狀況下,再也不是要麼偷要麼不偷,而是可偷商品的一部分。如何使用動態規劃來處理這種情形呢?
  答案是無法處理。使用動態規劃時,要麼考慮拿走整件商品,要麼考慮不拿,而無法判斷該不應拿走商品的一部分。
  但使用貪婪算法可輕鬆地處理這種狀況!首先,儘量多地拿價值最高的商品;若是拿光了,再儘量多地拿價值次高的商品,以此類推。
  例如,假設有以下商品可供選擇。

  藜麥比其餘商品都值錢,所以要儘可能往揹包中裝藜麥!若是可以在揹包中裝滿藜麥,結果就是最佳的。
  若是藜麥裝完後背包還沒滿,就接着裝入下一種最值錢的商品,以此類推。

 

 2.6 旅遊行程最優化

  假設你要去倫敦度假,假期兩天,但你想去遊覽的地方不少。你無法前往每一個地方遊覽,因此你列個單子。
  對於想去遊覽的每一個名勝,都列出所需的時間以及你有多想去看看。根據這個清單,你能確定該去遊覽哪些名勝嗎?
  這也是一個揹包問題!但約束條件不是揹包的容量,而是有限的時間;不是決定該裝入哪些商品,而是決定該去遊覽哪些名勝。請根據這個清單繪製動態規劃網格,再接着往下讀。
  網格相似於下面這樣。
  你畫對了嗎?請填充這個網格,決定該遊覽哪些名勝。答案以下。

 

2.7 處理相互依賴的狀況

  假設你還想去巴黎,所以在前述清單中又添加了幾項。
  去這些地方遊覽須要很長時間,由於你先得從倫敦前往巴黎,這須要半天時間。若是這3個地方都去玩,是否是要4.5天呢?
  不是的,由於不是去每一個地方都得先從倫敦到巴黎。到達巴黎後,每一個地方都只需1天時間。所以玩這3個地方須要的總時間爲3.5天(半天從倫敦到巴黎,每一個地方1天),而不是4.5天。
  將埃菲爾鐵塔加入「揹包」後,盧浮宮將更「便宜」:只要1天時間,而不是1.5天。如何使用動態規劃對這種狀況建模呢?
  沒辦法建模。動態規劃功能強大,它可以解決子問題並使用這些答案來解決大問題。但僅當每一個子問題都是離散的,即不依賴於其餘子問題時,動態規劃才管用。這意味着使用動態規劃算法解決不了去巴黎玩的問題。

 2.8 計算最終的解時會涉及兩個以上的子揹包嗎

  爲得到前述揹包問題的最優解,可能須要偷兩件以上的商品。但根據動態規劃算法的設計,最多隻需合併兩個子揹包,即根本不會涉及兩個以上的子揹包。不過這些子揹包可能又包含子揹包。

 

2.9 最優解可能致使揹包沒裝滿嗎

  徹底可能。假設你還能夠偷一顆鑽石。

  這顆鑽石很是大,重達3.5磅,價值100萬美圓,比其餘商品都值錢得多。你絕對應該把它給偷了!但當你這樣作時,餘下的容量只有0.5磅,別的什麼都裝不下。

 

 3 最長公共子串

  經過前面的動態規劃問題,你獲得了哪些啓示呢?

  • 動態規劃可幫助你在給定約束條件下找到最優解。在揹包問題中,你必須在揹包容量給定的狀況下,偷到價值最高的商品。
  • 在問題可分解爲彼此獨立且離散的子問題時,就可以使用動態規劃來解決。

  要設計出動態規劃解決方案可能很難,這正是本節要介紹的。下面是一些通用的小貼士。

  • 每種動態規劃解決方案都涉及網格。
  • 單元格中的值一般就是你要優化的值。在前面的揹包問題中,單元格的值爲商品的價值。
  • 每一個單元格都是一個子問題,所以你應考慮如何將問題分紅子問題,這有助於你找出網格的座標軸  下面再來看一個例子。假設你管理着網站dictionary.com。用戶在該網站輸入單詞時,你須要給出其定義。
      但若是用戶拼錯了,你必須猜想他本來要輸入的是什麼單詞。例如,Alex想查單詞fish,但不當心輸入了hish。在你的字典中,根本就沒有這樣的單詞,但有幾個相似的單詞。

  在這個例子中,只有兩個相似的單詞,真是過小兒科了。實際上,相似的單詞極可能有數千個。
  Alex輸入了hish,那他本來要輸入的是fish仍是vista呢?

 

 3.1 繪製網格

  用於解決這個問題的網格是什麼樣的呢?要肯定這一點,你得回答以下問題。

  • 單元格中的值是什麼?
  • 如何將這個問題劃分爲子問題?
  • 網格的座標軸是什麼?

  在動態規劃中,你要將某個指標最大化。在這個例子中,你要找出兩個單詞的最長公共子串。hish和fish都包含的最長子串是什麼呢?hish和vista呢?這就是你要計算的值。
  別忘了,單元格中的值一般就是你要優化的值。在這個例子中,這極可能是一個數字:兩個字符串都包含的最長子串的長度。
  如何將這個問題劃分爲子問題呢?你可能須要比較子串:不是比較hish和fish,而是先比較his和fis。每一個單元格都將包含這兩個子串的最長公共子串的長度。這也給你提供了線索,讓你以爲座標軸極可能是這兩個單詞。所以,網格可能相似於下面這樣。

  若是這在你看來猶如巫術,也不用擔憂。這些內容很難懂,但這也正是我到如今才介紹它們的緣由!

 

3.2 填充網格

  如今,你很清楚網格應是什麼樣的。填充該網格的每一個單元格時,該使用什麼樣的公式呢?因爲你已經知道答案——hish和fish的最長公共子串爲ish,所以能夠做點弊。
  即使如此,你仍是不能肯定該使用什麼樣的公式。計算機科學家有時會開玩笑說,那就使用費曼算法(Feynman algorithm)。這個算法是以著名物理學家理查德·費曼命名的,其步驟以下。
  (1) 將問題寫下來。
  (2) 好好思考。
  (3) 將答案寫下來。
  計算機科學家真是一羣不按常理出牌的人啊!
  實際上,根本沒有找出計算公式的簡單辦法,你必須經過嘗試才能找出管用的公式。有些算法並不是精確的解決步驟,而只是幫助你理清思路的框架。請嘗試爲這個問題找到計算單元格值的公式。給你一點提示吧:下面是這個單元格的一部分。

  其餘單元格的值呢?別忘了,每一個單元格都是一個子問題的值。爲什麼單元格(3, 3)的值爲2呢?又爲什麼單元格(3, 4)的值爲0呢?
  請找出計算公式,再接着往下讀。這樣即使你沒能找出正確的公式,後面的解釋也將容易理解得多。

 

3.3 揭曉答案

  最終的網格以下。


  我使用下面的公式來計算每一個單元格的值。
  實現這個公式的僞代碼相似於下面這樣。

if word_a[i] == word_b[j]:#兩個字母相同
    cell[i][j] = cell[i-1][j-1] + 1
else:#兩個字母不一樣
    cell[i][j] = 0 

 

  查找單詞hish和vista的最長公共子串時,網格以下。

  須要注意的一點是,這個問題的最終答案並不在最後一個單元格中!對於前面的揹包問題,最終答案老是在最後的單元格中。但對於最長公共子串問題,答案爲網格中最大的數字——它可能並不位於最後的單元格中。
  咱們回到最初的問題:哪一個單詞與hish更像?hish和fish的最長公共子串包含三個字母,而hish和vista的最長公共子串包含兩個字母。
所以Alex極可能本來要輸入的是fish。

 

3.4 最長公共子序列

  假設Alex不當心輸入了fosh,他本來想輸入的是fish仍是fort呢?
  咱們使用最長公共子串公式來比較它們。
  最長公共子串的長度相同,都包含兩個字母!但fosh與fish更像。

  這裏比較的是最長公共子串,但其實應比較最長公共子序列:兩個單詞中都有的序列包含的字母數。如何計算最長公共子序列呢?
  下面是用於計算fish和fosh的最長公共子序列的網格的一部分。

  你能找出填充這個網格時使用的公式嗎?最長公共子序列與最長公共子串很像,計算公式也很像。請試着找出這個公式——答案稍後揭曉。

 

3.5 最長公共子序列之解決方案

  最終的網格以下。

 

  下面是填寫各個單元格時使用的公式。

  僞代碼以下。

if word_a[i] == word_b[j]:#兩個字母相同
    cell[i][j] = cell[i-1][j-1] + 1 
else: #兩個字母不一樣
    cell[i][j] = max(cell[i-1][j], cell[i][j-1]) 

  本章到這裏就結束了!它絕對是本書最難理解的一章。動態規劃都有哪些實際應用呢?

  • 生物學家根據最長公共序列來肯定DNA鏈的類似性,進而判斷度兩種動物或疾病有多相似。最長公共序列還被用來尋找多發性硬化症治療方案。
  • 你使用過諸如git diff等命令嗎?它們指出兩個文件的差別,也是使用動態規劃實現的。
  • 前面討論了字符串的類似程度。編輯距離(levenshtein distance)指出了兩個字符串的相似程度,也是使用動態規劃計算獲得的。編輯距離算法的用途不少,從拼寫檢查到判斷用戶上傳的資料是不是盜版,都在其中。
  • 你使用過諸如Microsoft Word等具備斷字功能的應用程序嗎?它們如何肯定在什麼地方斷字以確保行長一致呢?使用動態規劃!

 

練習
  請繪製並填充用來計算blue和clues最長公共子串的網格。 

 

4 小結

  • 須要在給定約束條件下優化某種指標時,動態規劃頗有用。
  • 問題可分解爲離散子問題時,可以使用動態規劃來解決。
  • 每種動態規劃解決方案都涉及網格。
  • 單元格中的值一般就是你要優化的值。
  • 每一個單元格都是一個子問題,所以你須要考慮如何將問題分解爲子問題。
  • 沒有放之四海皆準的計算動態規劃解決方案的公式。
相關文章
相關標籤/搜索