动态规划钢条切割问题
动态规划(dynamic programming)与分治法类似。分治策略将问题划分为互不相交的
子问题,递归求解子问题,再将子问题进行组合,求解原问题。动态规划应用于子问题重叠
的情况,在这种情况下,分治法将会对重叠问题进行多次重复求解,而动态规划对每个子问题只求解一次
动态规划方法常用于求解**最优化问题(optimization problem)
**。这类问题可能有多个解,每个解有一个值,我们希望寻找具有最优值的解。
设计动态规划算法步骤:
- 刻画一个最优解的特征
- 递归定义最优解的值
- 计算最优解的值
- 利用计算出的信息构造一个最优解
例:钢条切割问题
假设钢条价格表
长度i | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
---|---|---|---|---|---|---|---|---|---|---|
价格p | 1 | 5 | 8 | 9 | 10 | 17 | 17 | 20 | 24 | 30 |
现在对于一根长度为n的钢条,求它能获得的最大效益r
注意到,每一次切割后将得到两根钢铁,下一步,又将这两根钢条看作两个独立的钢铁切割问题。通过组合子问题的最优解,选取组合收益最大者,构成原问题的最优解。钢条切割问题满足最优子结构性质:问题的最优解由相关子问题的最优解组合而成,而这些子问题都可以独立求解
。
对钢条问题,我们可以再进行简化。对于每次切割后得到的两段钢条,我们只对第二段进行再次切割,对第一段不再切割。
自顶向下递归实现
int CUD_ROD(int *p, int n) {
if (n == 0)return 0;
int q = INT_MIN;
for (int i = 1; i <= n;i++) {
if (p[i] + CUD_ROD(p, n - i) > q)q = p[i] + CUD_ROD(p, n - i);
}
return q;
}
输出价格数组p以及钢条长度n,返回最大收益r。
当n变大时,程序的运行时间会成倍增长,这是由于程序在递归调用时,对相同参数值进行了反复求解。例如,当n=5时,将会调用CUD_ROD(p, n=0 to 4)
,求解CUD_ROD(p, n=4)
时,又会调用CUD_ROD(p, n=0 to 3)
。整个程序中存在大量的重复求解过程。
使用动态规划方法求解最优钢条切割问题
朴素递归算法效率之所以低,是因为他在反复求解子问题。动态规划方法安排求解顺序,对每个子问题只进行一次求解,将其结果保存下来,再次遇到相同子问题时,只需要查询结果,而不需要重新计算。这是典型的时空权衡(time-memory trade-off)
。
带备忘的自顶向下法
仍按照自然递归的顺序进行编写,但过程中会保存子问题的解于数组r中。过程中首先查询子问题的解是否已经存在,若存在,直接返回解,不存在,则按常规方式进行求解。
int MEMOIZED_CUT_ROD_AUX(int *p, int n, int *r) {
if (r[n] >= 0) return r[n];