1. 程式人生 > >動態規劃之鋼條切割問題,《演算法導論》15.1

動態規劃之鋼條切割問題,《演算法導論》15.1

  • 動態規劃(dynamic programming),是一種解決問題的方法,它的思路是將大問題分解為子問題,然後合併子問題的解。與分治法將問題分解為彼此獨立的子問題不同,動態規劃應用於子問題相互重疊的情況。為了避免這些重疊部分(即子子問題)被重複計算,動態規劃演算法對每個子子問題只計算一次,並記錄在一個表格中,這正是programming名字的由來,取其表格之意。

  • 動態規劃通常用來求解“最優化問題”,這類問題可以有多種解決方案,每種解決方案對應一個值,我們希望找到具有最大值或最小值(最優值)的解決方案。注意,最優值只有一個,但是最優解決方案可能有多個,因為可能同時有多個解決方案都達到最優值。

    We typically apply dynamic programming to optimization problems. Such problems can have many possible solutions. Each solution has a value, and we wish to find a solution with the optimal (minimum or maximum) value. We call such a solution an optimal solution to the problem, as opposed to the optimal solution,since there may be several solutions that achieve the optimal value.

  • 解決這類問題的關鍵是找出所有的子問題,以及子問題之間的依賴關係。

    When we think about a dynamic-programming problem,we should understand the set of subproblems involved and how subproblems depend on one another.

  • 鋼條切割問題,給定一段長度為n的鋼條和一個價格表pi(i = 1,2,...,n),求切割鋼條方案,使得銷售收益r(n)最大。

#include<iostream>
using namespace std;

int p[10] = {1,5,8,9,10,17,17,20,24,30};    //p[i] means price when length = i+1

//引數p代表“長度-價格對照表”,n代表待切割鋼條總長度。函式列印最優值和一個最優解決方案,並返回最優值。
//這是一個從r[1]~r[n]自底向上的求解過程,耗時O(n^2)
int bottom_up_cut_rod(int p[], int n)
{
    int* r = new int[n+1]; //r[i] means max revenue when length = i
    int* s = new int[n];//s[i] means first piece size when length = i+1
    r[0] = 0;
    int rx,sx,tmp;
    for(int i = 1; i != n+1; ++i)
    {
        rx = -1;
        for(int j = 0; j != i; ++j)
        {
            tmp = p[i-j-1] + r[j];
            if(rx < tmp)
            {
                rx = tmp;
                sx = i-j;
            }
        }
        r[i] = rx;
        s[i-1] = sx;
    }
    //print the optimal value and a optimal solution
    cout << "r" << n << " = " << r[n] << ", 切割方案 " << n << " = ";
    for(int l = n; l != 0; )
    {
        cout << s[l-1];
        l = l - s[l-1];
        if(l)
            cout << "+";
    }
    cout << endl;

    rx = r[n];
    delete[] r;
    delete[] s;
    return rx;
}

void test()
{
    for(int  n = 0; n != 11; ++n)
        bottom_up_cut_rod(p, n);
}
/*output:
r0 = 0, 切割方案 0 = 
r1 = 1, 切割方案 1 = 1
r2 = 5, 切割方案 2 = 2
r3 = 8, 切割方案 3 = 3
r4 = 10, 切割方案 4 = 2+2
r5 = 13, 切割方案 5 = 3+2
r6 = 17, 切割方案 6 = 6
r7 = 18, 切割方案 7 = 6+1
r8 = 22, 切割方案 8 = 6+2
r9 = 25, 切割方案 9 = 6+3
r10 = 30, 切割方案 10 = 10
*/
  • 課後習題
  • 15.1-2 用總長度為3的鋼條就可以舉出反例。

    假設我們要對長度為3的鋼條進行切割,根據鋼條密度的定義,當鋼條長度為1、 2、 3時,密度依次為d1= p1, d2 = p2 / 2, d3 = p3/3;

    下面讓我們列舉所有可能切割方案以及總收益r:

    方案1:3 = 3, r1 = d3 * 3;
    方案2:3 = 2+1, r2 = d2 * 2 + d1;
    方案3:3 = 1+1+1, r3 = d1 * 3;

    按照題目所描述的“貪心”策略,在已知d2是三個密度中最大的情況下,就一定能得出方案2是最優解。
    用公式表達,就是:已知d(2)>d(1),d(2) > d(3), 有r2>r1,r2>r3。

    成不成立呢?已知d2>d1, 可得r2-r3 = 2(d2-d1) > 0, 於是r2>r3得證。

    而r2-r1 = 2(d2-d3)+(d1-d3), 只要(d1-d3)足夠小,就可以令r2-r1 < 0,就構成了反例。

    比如d1=1, d2=5, d3=4 就構成一個反例。