[Optimization] Dynamic programming

「就是迭代,被衆人說得這麼玄乎"html

「之因此歸爲優化,是由於動態規劃本質是一個systemetic bruce force"git

「由於systemetic,因此比窮舉好了許多,就認爲是優化的功績咯"github

 


 

一個熱身問題

不等長活動的安排

活動不等長,安排利用率最高的活動安排。算法

不一樣於「貪心算法」的例子,這裏但願活動地點的時間利用率儘可能的滿,而不是「爲知足更多的活動」。express

 

三個例子:數組

T(1) = f1-s1數據結構

T(2) = f2-s2ide

T(3) = T(1) + f3-s3,包含了子問題T(1);函數

核心思惟:post

T(3)時,就要優先保留住f3-s3,而後再看其餘"不衝突的items";這些"不衝突的items"實際上是個以前的子問題。

 

T(n) 不必定是最大,因此,最後要找出Table中的T(1)->T(n)中最大的,便是最優的。

 

 

 

進化而來的 "動態規劃"

最長公共子序列法 (LCS)

尋找子問題的思想

Ref: http://www.cnblogs.com/liyukuneed/archive/2013/05/22/3090597.html

動態規劃,衆所周知,第一步就是找子問題,也就是把一個大的問題分解成子問題。

A = "a0, a1, a2, ..., am-1",

B = "b0, b1, b2, ..., bn-1"。

若是am-1 == bn-1,則當前最長公共子序列爲"a0, a1, ..., am-2"與"b0, b1, ..., bn-2"的最長公共子序列與am-1的和。長度爲"a0, a1, ..., am-2"與"b0, b1, ..., bn-2"的最長公共子序列的長度+1。

  // 尾巴同樣,那確定能夠直接考慮「子問題」;

若是am-1 != bn-1,則最長公共子序列爲max("a0, a1, ..., am-2"與"b0, b1, ..., bn-1"的公共子序列,"a0, a1, ..., am-1"與"b0, b1, ..., bn-2"的公共子序列)

  // 尾巴若是不同,你的尾巴多是個人倒數第二個;個人尾巴也多是你的倒數第二個;兩者找一個最大的就好;

 

可視化爲「二維數組」

按照動態規劃的思想,對問題的求解,其實就是對子問題自底向上的計算過程。

這裏,計算c[i][j]時,c[i-1][j-1]、c[i-1][j]、c[i][j-1]已經計算出來了,這樣,咱們能夠根據X[i]與Y[j]的取值,按照上面的遞推,求出c[i][j],同時把路徑記錄在b[i][j]中(路徑只有3中方向:左上、左、上,以下圖)。

 

 

最長遞增子序列(LIS)

Ref: 最長遞增子序列

給定一個長度爲N的數組,找出一個最長的單調自增子序列(不必定連續,可是順序不能亂)。

例如:

給定一個長度爲6的數組A{5, 6, 7, 1, 2, 8},則其最長的單調遞增子序列爲{5,6,7,8},長度爲4.

 

解法一:利用LCS法

能夠把上面的問題轉化爲求最長公共子序列的問題。

(1) 排序A ----> 獲得子序列 B。

(2) A和B求LCS便可。

  

解法二:naive迭代法  O(N^2)

時間複雜度:從前到後遍歷每個elem,每一elem都要與以前的全部i 作比較,這樣時間複雜度爲O(N^2)。

這是簡單粗暴的方法。

 

解法三:動態規劃法  O(NlogN)

假設存在一個序列d[1..9] = 2 1 5 3 6 4 8 9 7,能夠看出來它的LIS長度爲5。

下面一步一步試着找出它。

咱們定義一個序列B,而後令 i = 1 to 9 逐個考察這個序列。

此外,咱們用一個變量Len來記錄如今最長算到多少了。

// 注意下面的「淘汰掉5」的過程

首先,把d[1]有序地放到B裏,令B[1] = 2,就是說當只有len=1 一個數字2的時候,長度爲1的LIS的最小末尾是2。這時Len=1

而後,把d[2]有序地放到B裏,令B[1] = 1,就是說長度爲1的LIS的最小末尾是1,d[1]=2已經沒用了,很容易理解吧。這時Len=1

接着,d[3] = 5,d[3]>B[1],因此令B[1+1]=B[2]=d[3]=5,就是說長度爲2的LIS的最小末尾是5,很容易理解吧。這時候B[1..2] = 1, 5,Len=2

再來,d[4] = 3,它正好加在1,5之間,放在1的位置顯然不合適,由於1小於3,長度爲1的LIS最小末尾應該是1,這樣很容易推知,長度爲2的LIS最小末尾是3,因而能夠把5淘汰掉,這時候B[1..2] = 1, 3,Len = 2

繼續,d[5] = 6,它在3後面,由於B[2] = 3, 而6在3後面,因而很容易能夠推知B[3] = 6, 這時B[1..3] = 1, 3, 6,仍是很容易理解吧? Len = 3 了噢。

第6個, d[6] = 4,你看它在3和6之間,因而咱們就能夠把6替換掉,獲得B[3] = 4。B[1..3] = 1, 3, 4, Len繼續等於3

第7個, d[7] = 8,它很大,比4大,嗯。因而B[4] = 8。Len變成4了

第8個, d[8] = 9,獲得B[5] = 9,嗯。Len繼續增大,到5了。

最後一個, d[9] = 7,它在B[3] = 4和B[4] = 8之間,因此咱們知道,最新的B[4] =7,B[1..5] = 1, 3, 4, 7, 9,Len = 5

因而咱們知道了LIS的長度爲5,且此時最後一個數字應該是 9。【有了這個線索,倒着遍歷,發現9後,就能夠過濾出想要的序列了】

注意。這個1,3,4,7,9不是LIS,它只是存儲的對應長度LIS的最小末尾。有了這個末尾,咱們就能夠一個一個地插入數據。

雖然最後一個d[9] = 7更新進去對於這組數據沒有什麼意義,可是若是後面再出現兩個數字 8 和 9,那麼就能夠把8更新到d[5], 9更新到d[6],得出LIS的長度爲6。

而後應該發現一件事情了:在B中插入數據是有序的,並且是進行替換而不須要挪動——也就是說,咱們可使用二分查找,將每個數字的插入時間優化到O(logN)~~~~~因而算法的時間複雜度就下降到了O(NlogN)

 

 

 

分解 「子問題」

硬幣找零

若是咱們有面值爲1元、3元和5元的硬幣若干枚,如何用最少的硬幣湊夠11元?

思惟方式

首先咱們思考一個問題,如何用最少的硬幣湊夠i元(i<11)?

1. 當咱們遇到一個大問題時,老是習慣把問題的規模變小,這樣便於分析討論。

2. 這個規模變小後的問題和原來的問題是同質的,除了規模變小,其它的都是同樣的, 本質上它仍是同一個問題(規模變小後的問題實際上是原問題的子問題)。

 

初始化

當i=0,即咱們須要多少個硬幣來湊夠0元。 因爲1,3,5都大於0,即沒有比0小的幣值,所以湊夠0元咱們最少須要0個硬幣。

咱們用 d(i)=j 來表示湊夠i元最少須要j個硬幣

因而咱們已經獲得了 d(0)=0, 表示湊夠0元最小須要0個硬幣。

 

  • 當i=1時,只有面值爲1元的硬幣可用, 所以咱們拿起一個面值爲1的硬幣,接下來只須要湊夠0元便可,而這個是已經知道答案的, 即 d(0)=0

因此,d(1)=d(1-1)+1=d(0)+1=0+1=1。

 

  • 當i=2時, 仍然只有面值爲1的硬幣可用,因而我拿起一個面值爲1的硬幣, 接下來我只須要再湊夠2-1=1元便可(記得要用最小的硬幣數量),而這個答案也已經知道了。

因此d(2)=d(2-1)+1=d(1)+1=1+1=2。

 

  • 當i=3時,咱們能用的硬幣就有兩種了:1元的和3元的。 既然能用的硬幣有兩種,我就有兩種方案。

若是我拿了一個1元的硬幣,個人剩下的目標就變爲了: 湊夠3-1=2元須要的最少硬幣數量。即d(3)=d(3-1)+1=d(2)+1=2+1=3。

若是我拿了一個3元的硬幣,個人剩下的目標就變爲了: 湊夠3-3=0元須要的最少硬幣數量。即d(3)=d(3-3)+1=d(0)+1=0+1=1。 

 

狀態轉移方程

這兩種方案哪一種更優呢? 記得咱們但是要用最少的硬幣數量來湊夠3元的。

因此, 選擇d(3)=1,怎麼來的呢?具體是這樣獲得的:d(3) = min{d(3-1)+1,  d(3-3)+1}

可見,這邊造成了一個三叉樹(由於有三種狀況1,3,5),而子問題就是當前問題的孩子,這些孩子已有了局部結果,直接用便可。

d(n) = min{ d(n-xi)+1 | i = types of coin} 

 

 

巨量的子問題

理解:N(i,j) = N(i,j-i) + N(i-1, j)

以上只是求最優的一個解,即:最少的coin的方案。這實際上是不少種組合中的一個。

那麼,原本有多少種組合呢?(有點像「沒有上級的宴會邀請問題」)

N(i, j) 

  • i:使用的面值最大的coin
  • j:要構成的總價值

N(1, 1000) : 只有一種方案。

N(2, 1000) : 有好多種方案,不少不少,怎麼算呢?

N(3, 1000) : 有更多種方案,不少不少,怎麼算呢?

 

/* 定使用i,即至少有一個i, 那麼,剩下的價值最大的可能(bound)就是j-i。

 * 不會使用i,會有多少種方案,這個子問題會提早被解。

 */

N(i,j) = N(i,j-i) + N(i-1, j)

 

N(i,j) = N(i,j-i) + N(i-1, j)

N(3,1000) = N(3,997) + N(2, 1000)
N(3,997)  N(2, 1000)
N(3,994) N(2, 997) N(2,998) N(1, 1000)
N(3,991) N(2,994) N(2,995) N(1,997) N(2,996) N(1,998)    
N(3,988); N(2,991) N(2,992); N(1,994) N(2,993); N(1,995)   N(2,994); N(1,996)      
...  ...  ...    ...       

可見,最後結果是個極其龐大的數字。

 

感性理解

爲什麼 N(2, 1000) = N(2, 998) + N(1, 1000) ?

假設1000不是所有由1構成,那麼,出如今其中的2就確定能夠移動到頂端。

又由於這裏是考慮的組合問題,而不是排列,因此,出現的2的這種狀況就確定能由x個左邊的狀況中的一種所表示。

其實就是:有2,或者沒2;若是有2,那就等於1000減去這個2;

這個思惟,與"疊海龜問題"中的」w+s最大的確定能放在最下面「的思想是一致的。

 

再次理解 「子問題」

Devise a dynamic programming algorithm that counts the number of non-decreasing
sequences of integers of length N, such that the numbers are between 0 and M
inclusive.

舉例:

#(3,2)表示:用數字1和2(條件是<=2)構成的len <=3的非遞減序列有多少種?

1 2 1,1  1,2  2,2 1,1,1  1,1,2 1,2,2  2,2,2 

 

#(5,10)= #(4,10)+ #(5,9)

考慮10時,

  • 等號右側左變量:有10,10已佔坑,只需考慮剩下前四個便可#(4,10)。
  • 等號右側右變量:沒10,最大隻能是9,5個數字要從10前面的item裏選擇。

子問題變成了只能向右下角(終點)推動的二維數組模式。

 

 

 

進一步練習  

揹包問題

Integer Knapsack Problem (Duplicate Items NOT Allowed)

You have n items (some of which can be identical); item Ii is of weight wi and value vi.
You also have a knapsack of capacity C. Choose a combination of available items

which all fit in the knapsack and whose value is as large as possible.

 

Matrix 的橫軸縱軸表示

子問題的表達:左黃 到 右黃 or 左黃 到 右下藍

 

矩陣解釋

數組f[i][j]:在只有i個物品,容量爲j的狀況下揹包問題的最優解.

當物品種類變大爲i+1時,最優解是什麼?

第i+1個物品,假設:

    • 能放進揹包(前提是放得下),那麼f[i+1][j]= f[i][j-weight[i+1]+value[i+1]
    • 若是不放進揹包,那麼f[i+1][j]= f[i][j]

這就得出了狀態轉移方程:

f[i+1][j]=max( f[i][j], f[i][j-weight[i+1]+value[i+1] )

 

手動舉例子

From: http://blog.csdn.net/mu399/article/details/7722810

條件:

    • 有編號分別爲a,b,c,d,e的五件物品,
    • 它們的重量分別是2,2,6,5,4,
    • 它們的價值分別是6,3,5,4,6,
    • 如今給你個承重爲10的揹包,

如何讓揹包裏裝入的物品具備最大的價值總和?  

現考慮 a4:

此時考慮a,但放不下了(此時的value=6是由於放了v(e) = 6)

接下來天然會想,是否是換一下袋子裏的這個東東,能得到更大的value呢?此時,表格對子問題的記錄就發揮做用了!

 

直接看sub-p:b2,看起來4-2->2有點 自動導航到所需子問題的味道。

結果是:你要分要放得下a,那麼,能獲得value爲9這個方案。這個方案看起來更好呦。

因此,思惟的關鍵就是要不要a的時候,看看兩種不一樣狀況下的value就行了。

 

Extended:

if Duplicate items allowed.

P = NP, 只能窮舉。

Extended:

數字分組問題,將問題轉化爲求揹包容量爲全部數總和一半的揹包問題。

 

 

生產線裝配問題

問題描述

下圖中能夠看出按照紅色箭頭方向進行裝配汽車最快,時間爲38。分別如今裝配線1上的裝配站一、3和6,裝配線2上裝配站二、4和5。

 

尋找子問題

(1) 描述經過工廠最快線路的結構

對於裝配線調度問題,一個問題的(找出經過裝配站Si,j 最快線路)最優解包含了子問題(找出經過S1,j-1或S2,j-1的最快線路)的一個最優解,這就是最優子結構

觀察一條經過裝配站S1,j (在裝配線1上) 的最快線路,會發現它一定是通過裝配線1或2上裝配站j-1。所以經過裝配站的最快線路只能如下兩者之一:

  a) 經過裝配線S1,j-1的最快線路,而後直接經過裝配站Si,j

  b) 經過裝配站S2,j-1的最快線路,從裝配線2移動到裝配線1,而後經過裝配線S1,j

爲了解決這個問題,即尋找經過一條裝配線上的裝配站j的最快線路,須要解決其子問題,即尋找經過兩條裝配線上的裝配站j-1的最快線路。

(子問題有兩條路線罷了)

 

(2) 一個遞歸的解

最終目標是肯定底盤經過工廠的全部路線的最快時間,設爲f*,令fi[j]表示一個底盤從起點到裝配站Si,j的最快時間,

則f* = min(f1[n]+x1, f2[n]+x2)。逐步向下推導,直到j=1。

    • 當j=1時:
      • f1[1] = e1+a1,1,  f2[1] = e2+a2,1
    • 當j>1時:
      • f1[j] = min(f1[j-1]+a1,jf2[j-1]+t2,j-1+a1,j),
      • f2[j] = min(f2[j-1]+a2,jf1[j-1]+t1,j-1+a2,j)。

Link: http://www.cnblogs.com/aabbcc/p/6509191.html

 

 

矩陣連乘

To evaluate (AB)C we need
(10 × 5) × 100 + (10 × 50) × 5 = 5000 + 2500 = 7500 multiplications;
To evaluate A(BC) we need
(100 × 50) × 5 + (10 × 50) × 100 = 25000 + 50000 = 75000 multiplications!

如何使計算量最小?

Ref: https://cnbin.github.io/blog/2015/12/19/ju-zhen-lian-cheng-dong-tai-gui-hua-xiang-jie/

 

最優子結構

(1) 找出最優解的性質,刻畫其特徵結構

 m[i][j] 表示第i個矩陣第j個矩陣這段的最優解。從 i --> j

將矩陣連乘積 簡記爲A[i:j] ,這裏i<=j。

假設這個最優解在第k處斷開,i<=k<j,由於A[i:j]是最優的,那麼A[i,k]A[k+1:j]也是相應矩陣連乘的最優解。  // <-- 總體最優,內部分割也最優

能夠用反證法證實之。 這就是最優子結構,也是用動態規劃法解題的重要特徵之一。

 

(2) 創建遞歸關係

設計算A[i:j],1≤i≤j≤n,所須要的最少數乘次數m[i,j],則原問題的最優值爲m[1,n] 。

  • 當i=j時,A[i,j]=Ai, m[i,j]=0;(表示只有一個矩陣,如A1,沒有和其餘矩陣相乘,故 乘的次數爲0)
  • 當i<j時,m[i,j] = min{ m[i,k] m[k+1,j] + pi-1*pk*pj } ,  其中 i<=k<j

至關於對i~j這段,把它分紅2段,看哪一種分法乘的次數最少,如:

A1,A2,A3,A4,則有3種分法:{A1}{A2A3A4}、{A1A2}{A3A4}、{A1A2A3}{A4},

其中,{}表示其內部是最優解,如{A1A2A3}表示是A1A2A3的最優解。

 

實踐出真知

對於 p={30, 35, 15, 5, 10, 20, 25}:

 

計算順序

每一個對角線算是一組;總共有以下六組。

表中是可能的全部組合狀況,須要計算選出每一個表格中最小的一個組合方式。

「左下」的計算就將成爲「右上」計算的 子問題集合!

 

對上例,共6個矩陣(A1~A6),n=6,

當r=3時,r循環裏面的是3個矩陣的最優解,i 從1->4,即 求的是    (r=3時 對角線是共四種狀況)

(A1 A2 A3), (A2 A3 A4), (A3 A4 A5), (A4 A5 A6) 這4個矩陣段 (長度爲3) 的最優解.

當i=2時,(A2 A3 A4) 的最優解爲 { A2 (A3 A4) ,  (A2 A3) A4 } 的較小值。

 

思惟技巧

花括號裏的東西不用計算了,由於以前已經計算過了,只須要查表找到最優的方式,以及min value直接用便可。

 

 

 

Graph 的路徑問題

Bellman-Ford算法

單源頭最短路徑,支持負權值

Ref: 幾個最短路徑算法Floyd、Dijkstra、Bellman-Ford、SPFA的比較

 

Dijkstra Algorithm

Dijkstra Algorithm Video: https://www.youtube.com/watch?v=RFEqcXSo_Zg 

Dijkstra 算法採用貪心算法(Greedy Algorithm)範式進行設計,普通實現的時間複雜度爲 O(V2),

若基於 Fibonacci heap 的最小優先隊列實現版本則時間複雜度爲 O(E + VlogV)。

 

Bellman-Ford Algorithm 

Bellman-Ford Algorithm 和 Dijkstra 算法同爲解決單源最短路徑的算法。對於帶權有向圖 G = (V, E),

    • Dijkstra 算法要求圖 G 中邊的權值均爲非負。  // 基於貪心算法,普通實現的時間複雜度爲 O(V2),若基於 Fibonacci heap 的最小優先隊列實現版本則時間複雜度爲 O(E + VlogV)
    • Bellman-Ford 算法能適應通常的狀況(即存在負權邊的狀況)。  // 基於動態規劃,O(V*E)

一個實現的很好的 Dijkstra 算法比 Bellman-Ford 算法的運行時間要低。

Bellman-Ford 算法採用動態規劃(Dynamic Programming)進行設計,實現的時間複雜度爲 O(V*E),其中 V 爲頂點數量,E 爲邊的數量。

 

Bellman-Ford 講解

油管講解: Bellman-Ford Algorithm Explained EASY

 

此連接講得很明瞭。例如:

iteration: 3 D --> 7

// 創建在「子問題」之上,也就是上一個「列」。

S走三步到D,那麼固然從上一次iter的兩步的基礎之上考慮!

  • 上一輪中"非無限"的,有哪些直達D呢?(下圖所示)
    • C直達,且第二次iter時C=5,因此,5+2=7成爲relax後的新值。 
    • F直達,且第二次iter時C=4,可是,4+3=7成爲relax後的新值,以上面的新值同樣。
  • 再看下一個E

可見這裏體現了時間複雜度爲O(V*E),就是矩陣的格子數。

【橫軸:從S開始走幾步能到達某個結點】

 

結果

最終的結果就是最後一列。

後一列比前一列更「優化」,值也就更「小」。

 

採用隊列繼續優化

Bellman-ford算法浪費了許多時間去作沒有必要的鬆弛,

而 SPFA算法 用隊列進行了優化,效果十分顯著,高效不可思議。(後續再研究)

 

 

Floyd Warshall Algorithm

多源最短路徑求法? 點擊圖片進入視頻連接。

 

 

初始狀態

Ref: 65 小甲魚數據結構與算法 最短路徑(弗洛伊德算法)

P矩陣告訴咱們:從v_x到v_y時必需要通過哪一個點。

  

 

時間複雜度O(n^3)

 

最終狀態

 

 

 

 

課後練習

Edit Distance

字符串變換的」最少操做「:

Given two text strings A of length n and B of length m, you
want to transform A into B. You are allowed to insert a character, delete a
character and to replace a character with another one. An insertion costs ci, a
deletion costs cd and a replacement costs cr.
Task: find the lowest total cost transformation of A into B

Ref: http://www.cnblogs.com/masterlibin/p/5785092.html

子問題:

 

 

Maximizing an expression

Instance: a sequence of numbers with operations +, −, × in between, for example
  1 + 2 − 3 × 6 − 1 − 2 × 3 − 5 × 7 + 2 − 8 × 9
Task: Place brackets in a way that the resulting expression has the largest possible
value.

Ref: https://courses.csail.mit.edu/6.006/fall10/psets/ps6/ps6-sol.pdf

答案:有點複雜,詳見連接。

 

Extended:

邏輯表達式中插入符號,使結果爲true。求有多少種方式。

上圖爲子問題:T(i, j) 的表達方式。

子問題看上去也得有O(n^2)個regression。

 

 

子字符串(隱含)出現的次數

We say that a sequence of Roman letters A occurs as a subsequence of a sequence
of Roman letters B if we can obtain A by deleting some of the symbols of B. Design
an algorithm which for every two sequences A and B gives the number of different
occurrences of A in B, i.e., the number of ways one can delete some of the symbols
of B to get A. For example, the sequence ba has three occurrences in the sequence
baba: baba, baba, baba.

From: https://stackoverflow.com/questions/6877249/find-the-number-of-occurrences-of-a-subsequence-in-a-string

Idea:

Based on Suffix matching.

The subproblem is to give the number of different occurences of substring of A in substring of B. (A的後綴字符串在B的後綴字符串出現的次數)


Algorithm:

    • In cell [row][col] write the value found at [row-1][col].

    • If sequence at row row and subsequence at column col start with the same char, add the value found at [row-1][col-1] to the value just written to [row][col].

 

 

求最大子數組的和 in O(n)

給定一個數組,它裏面全是一些數字,要找出不論什麼連續的值中最大的和. 例: 已有數組:{31, -41, 59, 26, -53, 58, 97, -93, -23, 84}

它的連續的值最大的和則是第 2 個值到第 6 個值的合:187.

若是用函數f(i)表示以第i個數字結尾的子數組的最大和,那麼咱們須要求出max(f[0...n])。咱們能夠給出以下遞歸公式求f(i)

這個公式的意義:

  1. 當以第(i-1)個數字爲結尾的子數組中全部數字的和f(i-1)小於0時,若是把這個負數和第i個數相加,獲得的結果反而不第i個數自己還要小,因此這種狀況下最大子數組和是第i個數自己。
  2. 若是以第(i-1)個數字爲結尾的子數組中全部數字的和f(i-1)大於0,與第i個數累加就獲得了以第i個數結尾的子數組中全部數字的和。   
31 -41 59 26 -53 58 97 -93 -23 84   
  -10   85 32 90 187 94 71 155  
31 -10 59 85 32 90 187 94 71 155  

 

動態規劃,衆所周知,第一步就是找子問題,也就是把一個大的問題分解成子問題。這裏咱們設兩個字符串A、B,A = "a0, a1, a2, ..., am-1",B = "b0, b1, b2, ..., bn-1"。

相關文章
相關標籤/搜索