DP 算法: cutting a rod

给定一个问题, 能够使用动态规划的方法解决的一个标志就是, 这个问题具有最优子结构(optimal substructure)。 我们可以认为, 能够使用DP算法的问题首先能够分解为子问题。 而且我们可以利用子问题的最优解去解决原始问题的解。 这当然需要我们在解决了子问题的时候, 设法对子问题的最优解存储起来。这就是所谓的DP ≈ recursion(递归) + memoirizaion(备忘)。

下面我们通过算法导论书上的cutting a rod 的例子来说明如何去使用DP算法。

一个rod 的长度是n, 对于不同长度的rod, 价格不同。 用Pi 代表长度为i 的rod 的价格(i = 1, 2, 3, ... n )。 价格表如下:

下在问题来了:

当给我们个长度为4 的 rod, 让我们选择一种切割方案,  使得我们得到的总的收益是最大的???

如上图, 箭头表示我们的选择, 要么切割, 要么不切两种可能。 所以总共有2^(4 - 1) = 8 种方案。 考虑到由于对称性导致的重复, 其实只有五(4 + 1)种方案, 如下:

    方案                              收益

(4, 0)                        9

(1, 3)                        9

(2, 2)                         10

(1, 1, 2)                  7

(1, 1, 1, 1,)          4

 

显然, 上述最佳的切割方案是(2, 2), 收益为10。

上述采用的是brute-force 算法, 显然时间复杂度能够到达O(2^(n-1)), 到达指数了, 显然效果很差, 算法伪代码如下:

解释, 进入循环:

i = 1, 表示在第一个长度单位的rod 是否切下来做决策, q = max(q, p[1] + CUT-ROD(p, n - 1)), 接下来就涉及到对于i = 1 的递归调用。

i = 2, 同理,  q = max(q, p[2] + CUT-ROD(p, n - 2)), 意思是第一次切割位于距离头为单位2的距离, 接下来, 对右边的那段进行递归调用。

............

i = n, 这种情况表示不切割, 还需要比较前面所有的切割方案中收益最大的值(存储在q)中, 然后和 不切割的方案比较, 去最大值就是我们的最大的收益。

上述算法达到exponential asymptotic time。

 

所以为了将算法的时间复杂度降下来, 我们使用dynamic programming:

这个问题为什么能用DP算法呢???

首先, 因为这个问题具有optimal substructure。  举个例子, 假设我们计算第一次切割在(3, 1)的最佳切割方案, 此时3 可以继续切割下去, 但是如果我们已经知道3 的最佳切割方案的收益的话(当然, 解决完子问题的时候, 需要将解存储下来), 我们直接采用3的最佳值直接加上1 即可以得到(3, 1)的后序最佳切割方案得到的收益值了。 这就是所谓的optimal substructure。 而且能够通过recursive 求解。


所以, 有如下注意的地方:

记录B(i) 为长度为i 的rod 采用最佳切割得到的收益。 所谓的最佳切割就是使得收益达到最大的切割。

那么, 我们有如下公式(Vk 代表Pk):

 

例如, 对于L = 8 的rod , 最大值B(8)的计算如下:

(1, 7), (2, 6), (3, 5), (4, 4), (5, 3), (6, 2), (7, 1), (8, 0)中得到。 pair 对应的第二个为需要求解的最佳值。

所以计算方法如下。

dynianc programming 的 bottom-up 形式的算法如下:

每当计算一个自问题的最佳解, 我们存储到表格中:

 依次类推下去, 最终我们到达B(8), 如下:

如下:

这样, 我们就解决了这个问题。

整个算法的步骤如下:

这样动态规划将时间复杂度从exponential time 降低到了polynomial time

算法的伪代码为:

 程序如下:

#include <iostream>
#include <cstring>

using namespace std;

const int N = 1000;
int p[11];
int r[N], s[N];

//initializer for prices and optimal solution
void init() {
    memset(r, -1, sizeof(r));
    r[0] = 0;
    p[0] = 0;
    p[1] = 1;
    p[2] = 5;
    p[3] = 8;
    p[4] = 9;
    p[5] = 10;
    p[6] = 17;
    p[7] = 17;
    p[8] = 20;
    p[9] = 24;
    p[10] = 30;
}

//naive exponential solution

int cutRod(int n) {
    int q = 0;
    for(int i = 1; i <= n; i++) {
            q = max(q, p[i] + cutRod(n - i));
    }
    return q;
}

//top-down solution
int topDownCutRod(int n) {
    if(r[n] != -1)
        return r[n];
    int q = 0;
    for(int i = 0; i <= n; i++) {
        q = max(q, p[i] + topDownCutRod(n - i));
    }
    return r[n];
}

//bottom-up solution
int bottomUpCutRod(int n) {
    if(r[n] != -1)
        return r[n];
    for(int j = 0; j <= n; j++) {
        int q = 0;
        for(int i = 1; i <= j; i++) {
            q = max(q, p[i] + r[j - 1]);
        }
        r[j] = q;
    }
    return r[n];
}

//bottom-up solution that maintains not only the best
//price but also the "required cut" for such solution

int extendedBottomUpCutRod(int n) {
    if(r[n] != -1)
        return r[n];
    for(int j = 1; j <= n; j++) {
        int q = 0;
        for(int i = 1; i <= j; i++) {
            if (q < p[i] + r[j - i]) {
                q = p[i] + r[j - i];
                s[j] = i;
            }
        }
        r[j] = q;

    }
    return r[n];
}

//print the extended method's output
void printCutRodSol(int n) {
    do {
        cout << s[n] << " ";
    } while((n -= s[n]) > 0);
}
int main() {
    init();
    int n;
    cout << "please input the length of the rod: " << endl;
    cin >> n;
    cout << endl;
//    cout << cutRod(4) << endl;
//    cout << cutRod(4) << endl;
    cout << extendedBottomUpCutRod(n) << endl;
    printCutRodSol(n);
    return 0;
}
运行结果如下:



 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值