轉自:http://www.cnblogs.com/yanlingyin/archive/2011/11/12/2246624.htmlhtml
對於動態規劃,我是這樣理解的:
把待解決的問題分爲一個規模較原問題小的子問題、算法
而後要考慮的就是如何更具這個子問題如何獲得原問題的解以及如何解決這個子問題編程
固然、原問題和子問題須要有相同的解決方式、它們只有問題規模的區別。數組
這樣講有點抽象、用一個簡單的圖來講明下:ide
能夠簡單的這樣理解、把原問題劃分爲小的問題(能組合成原問題的,小的問題再劃分、持續下去,找到簡單解函數
反方向計算回來(記下每一步結果)最後就能獲得解。學習
聽起來彷佛不難,可是要做比較深刻的理解仍是得經過實例說話優化
有向無環圖的最長簡單路徑:spa
對於通常的圖,求最長路徑並不向最短路徑那樣容易,由於最長路徑並無最優子結構的屬性。但DGA例外.net
問題描述:
給一個帶權有向無環圖G=(V,E),找出這個圖裏的最長路徑。
說實話初學者直接給出這個圖會看蒙的、再看看問題,不知道從何下手。
好了,對上圖作個簡單的處理:
如今看起來是否是清晰多了呢
用dilg(v)表示 以點結尾的最長路徑,如今考慮dilg(D), dilg(B), dilg(C)
dilg(D)=max{dilg(B)+1, dilg(C)+3}
來解釋一下:點D的入度邊有CD、BD。
以D結尾的最短路徑一定通過C、B中的一點;若是是C點,則以dilg(C)+3(權值)定會大於等於dilg(B)+1(權值)
若是沒能看懂,請注意dilg(V)的定義
對於任意的點V能夠有以下表達式:
dilg(v)=max{dilg(u)+w(u, v),(u,v)∈E}
這樣、問題dilg(V)就會被轉化爲較小的子問題dilg(U)(固然,U是連入V的點)
任何一個問題均可以用這樣的方式轉化、變小。
但總不能無限變小啊,最後回成爲最簡單的問題。當有兩個點,且其中的一個點入度爲0的時候如圖中的S-->C他們的最長距離就是
權值2。入門篇中說過,思考方向是從複雜到簡單,而計算方向是從簡單到複雜。
算法以下
Initialize all dilg(.) values to ∞;
1.Let S be the set of vertices with indegree=0; ////設集合S,裏面的是入度爲0的點
2.For each vertex v in S do
dilg(v)=0;
3. For each v∈V\S in Topological Sorting order do //對於V中除S外的點、按照拓撲排序的順序,依次求出最長路徑並保存好
dilg(v)=max{dilg(u)+w(u, v),(u,v)∈E} //拓撲排序能夠簡單的理解爲從起點到終點的最短路徑
4. Return the dilg(v) with maximum value.
如今是找到最長路徑的大小了、可是如何獲得最長路徑呢?
只需作點小小的改動:
Dplongestpath(G)
Initialize all dilg(.) values to ∞;
Let S be the set of vertices with indegree=0;
for each vertex v in S do
dist(v)=0;
4. For each v∈V\S in Topological Sorting order do
dilg(v)=max(u,v)∈E{dilg(u)+w(u, v)}
let (u,v) be the edge to get the maximum
value;
dad(v)=u;
5. Return the dilg(.) with maximum value.
每一步都記下V的父節點、最後根據dad()數組便可獲得路徑。
對於上面的問題:先找出子問題、而後解決由子問題如何獲得父問題以及如何把子問題分爲更小的子問題
注意:問題的本質沒有改變,只是規模變小。
個人理解:
動態規劃的目的正是在不改變問題本質的狀況下不斷縮小子問題的規模、規模很小的時候,也就能很容易獲得解啦(正如上面的只有兩個點的狀況)
上圖能夠這樣理解:
問題A5被分紅了子問題A三、A4解決它們就能解決A5,因爲A三、A4和A5有相同的結構(問題的本質沒變)因此A3能夠分爲問題A一、A2。固然A4也能
分爲兩個子問題,只是圖中沒畫出來。
下面的是再網上看到的不錯的思路:
Dynamic programming:
(1)problem is solved by identifying a collection of
subproblems,
(2) tackling them one by one, smallest rst,
(3) using the answers of small problems to help
figure out larger ones,
(4) until the whole lot of them is solved.
下面轉自:http://blog.csdn.net/qq_30137611/article/details/77655707
剛學動態規劃,或多或少都有一些困惑。今天咱們來看看什麼是動態規劃,以及他的應用。
學過度治方法的人都知道,分治方法是經過組合子問題來求解原問題,而動態規劃與分治方法類似,都是經過組合子問題的解來求解原問題,不一樣的是分治每次都產生一個新的子問題,而動態規劃會不必定產生新問題,可能產生重疊的子問題,即不一樣的子問題有公共的子子問題。
說了這麼多的比較苦澀的話,只是爲了回頭再看,咱們經過一個例子來具體說明一下:
小王剛來到一家公司,他的頂頭boss買了一條長度爲10的鋼條,boss讓小王將其切割爲短鋼條,使得這條鋼條的價值最大,小王應該如何作?咱們假設切割工序自己沒有成本支出。
已知鋼條的價格表以下:
長度 i | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
價格P(i) | 1 | 5 | 8 | 9 | 10 | 17 | 17 | 20 | 24 | 30 |
小王是一個很是聰明的人,馬上拿了張紙畫了一下當這根鋼條長度爲4的全部切割方案(將問題的規模縮小)
小王很快看出了能夠得到的最大收益爲5+5=10,他想了想,本身畫圖以及計算最大值的整個流程,整理了一下:
1>先畫出切一刀全部的切割方案:(1+8)、(5+5)、(8+1)一共三種可能,也就是 4 - 1中可能,把這個 4 換成 n(將具體狀況換成通常狀況),就變成了長度爲 n 的鋼條切一刀會有 n -1 中可能,而後在將一刀切過的鋼條再進行切割。
同上,(1+8)這個組合會有 2 中切法,(1+1+5)和(1+5+1)【看圖】,同理,(5+5)會有兩種切法(1+1+5)和(5+1+1),因爲(1+1+5)和上面的(1+1+5)重合,因此算一種切法,依次類推。
因爲咱們對 n-1個切點老是能夠選擇切割或不切割,因此長度爲 n 的鋼條共有 2^(n-1)中不一樣的切割方案不懂的點我
2>從這2^(n-1)中方案中選出能夠得到最大收益的一種方案
學過遞歸的小王很快就把上述過程抽象成了一個函數,寫出瞭如下的數學表達式:
設鋼條長度爲n的鋼條能夠得到的最大收益爲 r(n) (n>=1)
第一個參數P(n)表示不切割對應的方案,其餘 n-1個參數對應着另外 n-1中方案(對應上面的一刀切)
爲了求解規模爲 n 的原問題,咱們先求解形式徹底同樣,但規模更小的子問題。即當完成首次切割後,咱們將兩段鋼條當作兩個獨立的鋼條切割問題來對待。咱們經過組合兩個相關子問題的最優解,並在全部可能的兩段切割方案中選取組合收益最大者,構成原問題的最優解咱們成鋼條問題知足最優子結構性質
編程能力很強的小王拿出筆記本
很快的在電腦上寫下了以下代碼
#include <stdio.h> int CUT_ROD(int * p ,int n); int max(int q, int a); int main(void){ int i = 0; int p[10] = {1,5,8,9,10,17,17,20,24,30}; printf("請輸入鋼條的長度(正整數):\n"); scanf("%d",&i); int maxEarning = CUT_ROD(p,i); // 切割鋼條 printf("鋼條長度爲 %d 的鋼條所能得到的最大收益爲:%d\n",i,maxEarning); return 0; } // 切割鋼條 int CUT_ROD(int * p,int n){ int i; if(n < 0){ printf("您輸入的數據不合法!\n"); return -1; }else if(n == 0){ return 0; }else if(n > 10){ printf("您輸入的值過大!\n"); return -1; } int q = -1; for(i = 0; i < n;i++){ q = max(q,p[i] + CUT_ROD(p,n-1-i)); } return q; } int max(int q, int a){ if(q > a) return q; return a; }
沾沾自喜的小王拿着本身的代碼到boss面前,說已經搞定了。boss看了看他的代碼,微微一笑,說,你學過指數爆炸沒,你算算你的程序的時間複雜度是多少,看看還能不能進行優化?小王一聽蒙了,本身這些尚未想過,本身拿着筆和紙算了好大一會,得出了複雜度爲T(n) = 2^n,沒想到本身寫的代碼這麼爛,規模稍微變大就不行了。boss看了看小王,說:你想一想你的代碼效率爲何差?小王想了想,說道:「個人函數CUT-ROD反覆地利用相同的參數值對自身進行遞歸調用,它反覆求解了相同的子問題了」boss說:「還不錯嘛?知道問題出在哪裏了,那你怎樣解決呢?」小王搖了搖頭,boss說:「你應該聽過動態規劃吧,你能夠用數組把你對子問題求解的值存起來,後面若是要求解相同的子問題,直接用以前的值就能夠了,動態規劃方法是付出額外的內存空間來節省計算時間,是典型的時空權衡」,聽到這裏小王暗自佩服眼前的boss,姜仍是老的辣呀。
boss說完拿起小王的筆記本,寫下了以下代碼:
// 帶備忘的自頂向下法 求解最優鋼條切割問題 #include <stdio.h> int MEMOIZED_CUT_ROD(int * p,int n); int MEMOIZED_CUT_ROD_AUX(int * p, int n, int * r); int max(int q,int s); int main(void){ int n; int p[11]={-1,1,5,8,9,10,17,17,20,24,30}; printf("請輸入鋼條的長度(正整數 < 10):\n"); scanf("%d",&n); if(n < 0 || n >10){ printf("您輸入的值有誤!"); }else{ int r = MEMOIZED_CUT_ROD(p,n); printf("長度爲%d的鋼條所能得到的最大收益爲:%d\n",n,r); } return 0; } int MEMOIZED_CUT_ROD(int * p, int n){ int r[20]; for(int i = 0; i <= n; i++){ r[i] = -1; } return MEMOIZED_CUT_ROD_AUX(p,n,r); } int MEMOIZED_CUT_ROD_AUX(int * p, int n, int * r){ if(r[n] >= 0){ return r[n]; } if(n == 0){ return 0; }else{ int q = -1; for(int i = 1; i <= n; i++){// 切割鋼條, 大綱有 n 中方案 q = max(q,p[i] + MEMOIZED_CUT_ROD_AUX(p,n-i,r)); } r[n] = q;// 備忘 return q; } } int max(int q, int s){ if(q > s){ return q; } return s; }
小王兩眼瞪的直直的。boss好厲害,我這剛入職的小白還得好好修煉呀。
寫完boss說:這中方法被稱爲帶備忘的自頂向下法。這個方法按天然的遞歸形式編寫過程,但過程會保存每一個子問題的解(一般保存在一個數組或散列表中),當須要一個子問題的解時,過程首先檢查是否已經保存過此解,若是是,則直接返回保存的值。還有一種自底向上法。這種方法通常須要恰當定義子問題的規模,使得任何子問題的求解都只依賴於「更小的」子問題的求解。於是咱們能夠將子問題按規模排序,按由小至大的順序進行求解,當求解某個子問題是,它所依賴的那些更小的子問題都已經求解完畢,結果已經保存,每一個子問題只需求解一次。若是你有興趣回去好好看看書本身下去好好研究下吧。
最後再考考你,我寫的這個帶備忘的自頂向下法的時間複雜度是多少?小王看了看代碼,又是循環,又是遞歸的,腦子都轉暈了。boss接着說:「你能夠這樣想,我這個函數是否是對規模爲0,1,…,n的問題進行了求解,那麼你看,當我求解規模爲n的子問題時,for循環是否是迭代了n次,由於在我整個n規模的體系中,每一個子問題只求解一次,也就是說我for循環裏的遞歸直接返回的是以前已經計算了的值,好比說 求解 n =3的時候,for(int i = 1,i <=3;i++),循環體執行三次,n=4時,循環體執行四次,因此說,我這個函數MEMOIZED_CUT_ROD進行的全部遞歸調用執行此for循環的迭代次數是一個等差數列,其和是O(n^2)」,是否是效率高了許多。小王嗯嗯直點頭,想着回去得買本《算法導論》好好看看。
無後效性是一個問題能夠用動態規劃求解的標誌之一,理解無後效性對求解動態規劃類題目很是重要
轉自:http://blog.csdn.net/qq_30137611/article/details/77655707
某階段的狀態一旦肯定,則此後過程的演變再也不受此前各類狀態及決策的影響
百度百科是這樣定義的,是否是很苦澀,難懂。而且網上對這個名詞的解釋大多都是理論性的,很差理解,今天咱們經過一個例子來看看什麼是無後效性
如今有一個四乘四的網格,左上角有一個棋子,棋子每次只能往下走或者往右走,如今要讓棋子走到右下角
假設棋子走到了第二行第三列,記爲s(2,3),以下圖,畫了兩條路線和一條不符合題意的路線,那麼當前的棋子[s(2,3)位置]怎麼走到右下角和以前棋子是如何走到s(2,3)這個位置無關[不論是黑色尖頭的路線仍是藍色箭頭的路線]
換句話說,當位於s(2,3)的棋子要進行決策(向右或者向下走)的時候,以前棋子是如何走到s(2,3)這個位置的是不會影響我作這個決策的。以前的決策不會影響了將來的決策(以前和將來相對於如今棋子位於s(2,3)的時刻),這就是無後效性,也就是所謂的「將來與過去無關」
看完了無後效性,那咱們再來看看有後效性,仍是剛纔的例子,只不過如今題目的條件變了,如今棋子能夠上下左右走可是不能走重複的格子
那麼如今紅色箭頭就是一個合法的路線了,當個人棋子走到了s(2,3)這個位置的時候,要進行下一步的決策的時候,這時候的決策是受以前棋子是如何走到s(2,3)的決策的影響的,好比說紅色箭頭的路線,若是是紅色箭頭決策而造成的路線,那麼我下一步決策就不能往下走了[由於題意要求不能走重複的格子],以前的決策影響了將來的決策,」以前影響了將來」,這就叫作有後效性
在此感謝騰訊大神的指導,學習離不開本身的努力和名師的指導。