算法導論-動態規劃(鋼條切割問題)

  動態規劃(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 = i+ i2 + i3 +...+ ik,獲得最大收益 r = 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 的兩段,接着求解這兩段的最優切割收益r和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]。

輸出:

相關文章
相關標籤/搜索