算法導論-動態規劃-鋼條切割

動態規劃一般用於解決最優化問題,在這類問題中,經過作出一組選擇來達到最優解。在作出每一個選擇的同時,一般會生成與原問題形式相同的子問題。當多於一個選擇子集都生成相同的子問題時,動態規劃技術一般就會頗有效,其關鍵技術就是對每一個這樣的子問題都保存其解,當其重複出現時便可避免重複求解。ios

 

鋼條切割問題算法

Serling公司購買長鋼條,將其切割爲短鋼條出售。切割工序自己沒有成本支出。公司管理層但願知道最佳的切割方案。假定咱們知道Serling公司出售一段長爲i英寸的鋼條的價格爲pi(i=1,2,…,單位爲美圓)。鋼條的長度均爲整英寸。圖15-1給出了一個價格表的樣例。數組

鋼條切割問題是這樣的:給定一段長度爲n英寸的鋼條和一個價格表pi(i=1,2,…n),求切割鋼條方案,使得銷售收益rn最大。注意,若是長度爲n英寸的鋼條的價格pn足夠大,最優解可能就是徹底不須要切割。函數

 

1、問題分析測試

長度爲n英寸的鋼條共有2n-1種不一樣的切割方案,由於在距離鋼條左端i(i=1,2,…n)英寸處,老是能夠選擇切割或不切割。優化

若是一個最優解將鋼條切割爲k段(對某個),那麼最優切割方案,將鋼條切割爲長度分別爲i1,i2...ik的小段獲得的最大收益爲spa

對於其中,pn對應不切割,對於每一個i=1,2,…,n-1,首先將鋼條切割爲長度爲i和n-i的兩段,接着求解這兩段的最優切割收益ri和rn-i(每種方案的最優收益爲兩段的最優收益之和)。當完成首次切割後,咱們將兩段鋼條當作兩個獨立的鋼條切割問題實例。經過組合兩個相關子問題的最優解,並在全部可能的兩段切割方案中選取組合收益最大者,構成原問題的最優解。3d

 

鋼條切割問題還存在一種類似的但更爲簡單的遞歸求解方法:將鋼條從左邊切割下長度爲i的一段,只對右邊剩下的長度爲n-i的一段繼續進行切割,對左邊的一段則再也不進行切割。這樣獲得的公式爲:。這樣原問題的最優解只包含一個相關子問題(右端剩餘部分)的解,而不是兩個。code

 

2、算法實現blog

一、樸素遞歸算法

自頂向下遞歸實現參考代碼爲:

int CutRod(const int *p, int n)
{
    if (n == 0)
    {
        return 0;
    }

    int q = -1;
    for (int i = 1; i <= n; ++i)
    {
        int tmp = p[i] + CutRod(p, n - i);
        if (q < tmp)
        {
            q = tmp;
        }
    }

    return q;
}

分析:自頂向下遞歸實現的CutRod效率不好,緣由在於CutRod反覆地用相同的參數值對自身進行遞歸調用,即它反覆求解相同的子問題。它的運行時間爲對於長度爲n的鋼條CutRod考察了全部2n-1種可能的切割方案。遞歸調用樹共有2n-1個葉結點,每一個葉結點對應一種可能的切割方案。

 

二、動態規劃算法

樸素遞歸算法之因此效率很低,是由於它反覆求解相同的子問題。所以,動態規劃方法仔細安排求解順序,對每一個子問題只求解一次,並將結果保存下來。若是隨後再次須要此子問題的解,只需查找保存的結果,而沒必要從新計算。所以,動態規劃方法是付出額外的內存空間來節省計算空間。

動態規劃有兩種等價的實現方法。

(1)帶備忘的自頂向下法

此方法仍按天然的遞歸形式編寫過程,但過程會保存每一個子問題的解(一般保存在一個數組或散列表中)。當須要一個子問題的解時,過程首先檢查是否已經保存過此解。若是是,則直接返回保存的值,從而節省了計算時間;不然,按一般方式計算這個子問題。

(2)自底向上法

這種方法通常須要恰當定義子問題「規模」的概念,使得任何子問題的求解都只依賴於「更小的」子問題的求解。所以,咱們能夠將子問題按照規模順序,由小至大順序進行求解。當求解某個子問題時,它所依賴的那些更小的子問題都已求解完畢,結果已經保存。每一個子問題只需求解一次,當咱們求解它時,它的全部前提子問題都已求解完成。

說明:兩種方法獲得的算法具備相同的漸進運行時間,僅有的差別是在某些特殊狀況下,自頂向下方法並未真正遞歸地考察全部可能的子問題。因爲沒有頻繁的遞歸函數調用的開銷,自底向上方法的時間複雜度函數一般具備更小的係數。

 

下面給出動態規劃-帶備忘的自頂向下過程參考代碼:

 1 int MemoizedCutRodAux(const int *p, int n, int *r)
 2 {
 3     if (r[n] >= 0)
 4     {
 5         return r[n];            //首先檢查所需的值是否存在
 6     }
 7 
 8     int q = -1;
 9     if (n == 0)
10     {
11         q = 0;
12     }
13     else
14     {
15         for (int i = 1; i <= n; ++i)
16         {
17             int tmp = p[i] + MemoizedCutRodAux(p, n - i, r);
18             if (q < tmp)
19             {
20                 q = tmp;
21             }
22         }
23     }
24     r[n] = q;
25 
26     return q;
27 }
28 
29 int MemoizedCutRod(const int *p, int n)
30 {
31     int *r = new int[n + 1];
32     for (int i = 0; i <= n; ++i)
33     {
34         r[i] = -1;
35     }
36 
37     return MemoizedCutRodAux(p, n, r);
38 }

 

下面給出動態規劃-自底向上過程參考代碼:

int BottomUpCutRod(const int *p, int n)
{
    int *r = new int[n + 1];
    r[0] = 0;

    for (int i = 1; i <= n; ++i)
    {
        int q = -1;
        for (int j = 1; j <= i; ++j)
        {
            int tmp = p[j] + r[i - j];
            q = q > tmp ? q : tmp;
        }
        r[i] = q;
    }

    return r[n];
}

說明:自頂向下與自底向上算法具備相同的漸進運行時間

 

最後給出重構解參考代碼:

 1 #include <iostream>
 2 using namespace std;
 3 
 4 void ExtendedBUCutRod(const int *p, int n, int *r, int *s)
 5 {
 6     r[0] = 0;
 7     for (int i = 1; i <= n; ++i)
 8     {
 9         int q = -1;
10         for (int j = 1; j <= i; ++j)
11         {
12             int tmp = p[j - 1] + r[i - j];
13             if (q < tmp)
14             {
15                 q = tmp;
16                 s[i] = j;
17             }
18         }
19         r[i] = q;
20     }
21 }
22 
23 //r[]保存長度爲i的鋼條最大收益,s[]保存最優解對應的第一段鋼條的切割長度
24 void PrintBUCutRod(const int *p, int n, int *r, int *s)
25 {
26     ExtendedBUCutRod(p, n, r, s);
27     cout << "長度爲" << n << "的鋼條最大收益爲:" << r[n] << endl;
28 
29     cout << "最優方案的鋼條長度分別爲:";
30     while (n > 0)
31     {
32         cout << s[n] << " ";
33         n = n - s[n];
34     }
35     cout << endl;
36 }
37 
38 //Test
39 int main()
40 {
41     int n;
42     while (cin >> n)
43     {
44         int *p = new int[n];
45         for (int i = 0; i < n; ++i)
46         {
47             cin >> p[i];
48         }
49         int *r = new int[n + 1];
50         int *s = new int[n + 1];
51 
52         PrintBUCutRod(p, n, r, s);
53     }
54 
55     return 0;
56 }

一個測試案例爲:

相關文章
相關標籤/搜索