動態規劃(dynamic programming)與分之方法類似,都是經過組合子問題的解來求解原問題。分治方法將問題劃分爲互不相交的子問題,遞歸地求解子問題,再將它們的解組合起來,求出原問題的解(如歸併排序)。而動態規劃應用於子問題重疊的狀況,即不一樣的子問題具備公共的子子問題(子問題的求解是遞歸進行的,將其劃分爲更小的子子問題)。在這種狀況下,分治算法會作許多沒必要要的工做,它會反覆地求解那些公共子子問題。而動態規劃算法對每一個子子問題只求解一次,將其解保存在一個表格中,從而無需每次求解一個子子問題時都從新計算,避免了沒必要要的計算工做。算法
一般按以下4個步驟來設計一個動態規劃算法:數組
1.刻畫一個最優解的結構特徵。spa
2.遞歸地定義最優解的值。設計
3.計算最優解的值,一般採用自底向上的方法。code
4.利用計算出的信息構造一個最優解。blog
例子:鋼條切割問題排序
描述:給定一段長度爲n英寸的鋼條和一個價格表pi(i = 1,2,3...,n)求切割鋼條方案,使得銷售收益rn最大。
遞歸
鋼條價格表內存
長度i | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
價格pi | 1 | 5 | 8 | 9 | 10 | 17 | 17 | 20 | 24 | 30 |
若是一個最優解將鋼條切割爲k段(1≤k≤n),那麼最優切割方案 n = i1 + i2 + i3 +...+ ik,獲得最大收益 rn = pi1 + pi2 + pi3 + ... + pik。更通常地,對於 rn(n ≥ 1),咱們能夠用更短的鋼條的最優切割收益來描述它:rn = max(pn , r1 + rn-1 , r2 + rn-2, ..., rn-1 + r1),第一個參數pn對應不切割,直接出售長度爲n的鋼條的方案。其餘n-1個參數對應另外n-1種方案:對每一個i = 1,2,...,n-1,首先將鋼條切割爲長度爲i 和 n -i 的兩段,接着求解這兩段的最優切割收益ri 和rn-i。因爲沒法預知哪一種方案會得到最優收益,咱們必須考察全部可能的i,選取其中收益最大者。
table
注意到,爲求解規模爲n的原問題,咱們先求解形式徹底同樣,但規模更小的子問題。即當完成首次切割後,咱們將兩段鋼條當作兩個獨立的鋼條切割問題實例。經過組合兩個關於子問題的最優解,並在全部可能的切割方案中選取組合收益最大者,構成原問題的最優解。動態規劃方法仔細安排求解順序,對每一個子問題只求解一次,並將結果保存下來。是付出額外的內存空間來節省計算時間。有兩種等價的實現方法:
一,帶備忘的自頂向下法。此方法按天然的遞歸形式編寫過程,但過程會保存每一個子問題的解,當須要一個子問題的解時,過程首先檢查是否已經保存過此解。若是是,則直接返回保存的值,從而節省了計算時間;不然,按一般方式計算這個子問題。
二,自底向上法。這種方法通常須要恰當定義子問題"規模"的概念,使得任何子問題的求解只依賴於"更小"的子問題的求解。於是能夠將子問題按規模排序,按由小到大的順序進行求解。當求解某個子問題時,它所依賴的那些更小的子問題都已求解完畢,結果已經保存。
自底向上法代碼以下:
1 void bottomUpCutRod(const vector<int> &p,int n , vector<int> &r) 2 { 3 r.resize(n+1); 4 r[0] = 0; 5 int q = -1; 6 7 for(int j = 1; j <= n; ++j){ 8 q = -1; 9 for(int i = 1; i <= j; ++i){ 10 q = std::max(q,p[i] + r[j-i]); 11 } 12 r[j] = q; 13 } 14 }
例子:
1 int main() 2 { 3 int p[] = {-1,1,5,8,9,10,17,17,20,24,30}; 4 vector<int> vp(p,p + sizeof(p)/sizeof(int)); 5 vector<int> result; 6 bottomUpCutRod(vp,9,result); 7 for(int i = 1; i <=9; ++i) 8 cout << result[i] << " "; 9 }
結果保存在result數組中,長度爲i的鋼條切割的最優收益值爲result[i]。
輸出: