动态规划(Dynamic Programming)

Dynamic programming, like the divide-and-conquer method, solves problems by
combining the solutions to subproblems. (“Programming” in this context refers
to a tabular method, not to writing computer code.)

动态规划分治法 相似, 都是通过 组合子问题的解 来 求解原问题. (“programing” 指的是一种 表格法, 而不是编写计算机程序.)

分治法 将问题划分为 互不相交的子问题, 递归的求解子问题, 再将它们的解组合起来, 求出原问题的解.
动态规划 应用于 子问题重叠 的情况, 即不同的子问题具有公共的 子子问题, 动态规划算法对每个 子子问题 只求解一次, 将其解放入一个 表格 中, 从而无需每次求解一个 子子问题时都重新计算.

动态规划通常用于求解 最优化问题(optimization problem).
这类问题可以有很多 可行解, 每个解都有一个值, 我们希望寻找 具有最优值(最大或最小)的解.
我们称这样的解为问题的 一个最优解(an optimal solution), 而不是 最优解(the optimal solution), 因为可能有多个解都达到最优值.

1 钢条切割(Rod cutting)

给定一段长度为 n 英寸的钢条 和一个价格表 pi (i=1, 2, …, n), 求切割钢条方案, 使得销售收益 rn 最大.

价格表样例:
dynamicPrograming_1
考虑 n=4 的情况, 下图给出了 4 英寸钢条所有可能的切割方案.
dynamicPrograming_2
长度为 n 英寸的钢条 共有 2n-1 种不同的切割方案, 因为在钢条左端 i(i=1, 2, …, n-1) 英寸处, 我们总有切割或不切割两种选择.

我们用 rn 表示 长度为 n 个钢条得到的最大收益.
对于上述价格表, 最优解方案 ri (i=1, 2, …, 10) 如下:
dynamicPrograming_3
更一般地, 对于 rn (n>=1), 我们可以用更短的钢条的最优切割收益来描述它:
rn = max(pn, r1+rn-1, r2+rn-2, …, rn-1+r1)
第一个参数 pn 表示不切割.
其他 n-1 个参数对应另外 n-1 种方案: 对每个 i=1, 2, …, n-1, 首先将钢条切割为长度为 i 和 n-i 的两段, 接着求解这两段的最优切割收益 ri 和 rn-i (每种方案的最优收益为两段的最优收益之和).

这里直接给出 自底向上法(bottom-up method) 的代码:

public class CutRod {
    int bottomUpCutRod(int[] price, int n) {
        // dp 数组/表格 用于存放最优解
        int[] dp = new int[n + 1];
        dp[0] = 0;
        for (int i = 1; i < Math.min(price.length + 1, n + 1); i++) {
            dp[i] = price[i - 1];
        }
        // 规模为i的子问题
        for (int i = 1; i <= n; i++) {
            int m = 0;
            for (int j = 1; j <= i; j++) {
                m = Math.max(m, dp[j] + dp[i - j]);
            }
            dp[i] = m;
        }
        return dp[n];
    }

    public static void main(String[] args) {
        //定义一个数组存储对应钢条长度的售价
        int[] p = {1, 5, 8, 9, 10, 17, 17, 20, 24, 30};
        CutRod cutRod = new CutRod();
        System.out.println(cutRod.bottomUpCutRod(p, 14));
    }
}

所谓 自底向上法, 就是先求解最小规模的子问题, 然后再求解更大规模的子问题.
上面的解法使用 dp[1..n+1] 数组(表格) 存放钢条长度为 1n+1 的最优解.
dp[0]=0表示钢条长度为 0 时收益为 0, 这样做的话方便写子问题求解的循环.

以上程序的执行过程:
先初始化 最优解数组 dp[], 把 price[] 拷贝给 dp[], 此时 dp[] 里的元素还不全是最优值;
求解长度为 1 的最优解: 就是 dp[1]=dp[1](也是price[1]);
求解长度为 2 的最优解: dp[2]= max(dp[1]+dp[1], dp[2])
求解长度为 3 的最优解: dp[3]=max(dp[1]+dp[2], dp[2]+dp[1], dp[3]);
求解长度为 n 的最优解: dp[n]=max(dp[1]+dp[n-1], dp[2]+dp[n-2], …, dp[i]+dp[n-i], …, dp[n]);
dp[n] 就是 长度为 n 的钢条的最优收益, 返回 dp[n].

以上算法的时间复杂度为 O(n*n).

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值