您的位置:首页 > 其它

动态规划 钢条切割

2015-09-03 17:24 211 查看

动态规划 钢条切割

《算法导论》第15章讲动态规划,写着属于高级设计和分析技术内容。

前言

动态规划和分治像而不同,像体现在都是组合子问题的解来求解原问题,不同体现在:分治是原问题分解为互不相交的子问题(可以回想归并排序和快速排序过程),分别递归地求解子问题,再将子问题的解组合起来,求出原问题的解;动态规划中分解原问题是分解为子问题重叠的情况,即不同的子问题具有公共的子子问题,这种情况,分治算法会重复计算公共子问题的解,做了很多不必要的工作,而动态规划是对每个子问题只求解一次,将其解保存在一个表格中,从而不需要每次求解子问题都重新计算,避免了这种不必要的计算工作。

通常按如下4个步骤来设计一个动态规划算法:

刻画一个最优解的结构特征;

递归地定义最优解的值;

计算最优解的值,通常采用自底向上的方法;

利用计算出的信息构造一个最优解。

如果我们仅仅求一个最优解的值,而非解本身,可以忽略步骤4;如果是求解本身,需要执行步骤4,同时需要再步骤3中维护一些额外信息,以便来构造最优解。

最优子结构性质:问题的最优解由相关子问题的最优解组合而成,而这些子问题可以独立求解。

最优子结构性质和子问题重叠性质是应用动态规划的两个标志。

备注:快速排序本质思想也是分治,因为是原地排序,子问题递归求解后不需要再合并。

1.钢条切割

通过钢条切割问题引出动态规划。

长度为n的钢条求能使公司收益最大的切割方案。给的条件也有限制,长度为n的钢条,price


1.1 朴素的递归解法

将钢条从左边切割下长度为i的一段,只对右边剩下的长度为n-i的一段进行再切割(递归求解),对左边的一段则不再进行切割。这样可以得出收益r的公式:

在这个公式中,原问题的最优解只包含一个相关子问题(右端剩余的部分)的解,而不是两个。

int CutRod(vector<int> price, int n) {
if (n == 0)
return 0;
int res = -1;
/* 也可以i从0开始到n-1 */
for(i = 1; i <= n; i ++) {
res = max(res, price[i] + CutRod(price, n - i));
}
return res;
}


显然递归的效率是很差的,当n比较大时会很慢。取n=4,查看其递归调用树。



分析CutRod()的运行时间,令T
表示第二个参数值为n时,CutRod的调用次数,该值等于递归调用树中根为n的子树中的结点个数,blablabla,CutRod的运行时间是n的指数函数。

所以将CutRod()转换为一个更高效的动态规划算法。动态规划算法方法是付出额外的内存空间来节省计算时间,是典型的时空权衡(time-memory-trade-off)的例子,可以将一个指数时间的解转化为一个多项式时间的解。

1.2 动态规划解法

动态规划有两种等价的实现方法。

(1)带备忘的自顶向下法

此方法仍按自然的递归形式编写过程,但过程会保存每个子问题的解(通常保存在一个数组或散列表中)。当需要一个子问题的解时,过程首先检查是否已经保存过此解。如果是,则直接返回保存的值,从而节省了计算时间;否则,按通常方式计算这个子问题。

(2)自底向上法

这种方法一般需要恰当定义子问题“规模”的概念,使得任何子问题的求解都只依赖于“更小的”子问题的求解。因此,我们可以将子问题按照规模顺序,由小至大顺序进行求解。当求解某个子问题时,它所依赖的那些更小的子问题都已求解完毕,结果已经保存。每个子问题只需求解一次,当我们求解它时,它的所有前提子问题都已求解完成。

说明:两种方法得到的算法具有相同的渐进运行时间,仅有的差异是在某些特殊情况下,自顶向下方法并未真正递归地考察所有可能的子问题。由于没有频繁的递归函数调用的开销,自底向上方法的时间复杂度函数通常具有更小的系数。

带备忘的自顶向下CutRod的解法:

int MemoizedCutRod(vector<int> price, int n) {
vector<int> res(n + 1, -1);
return Memoized(price, res, n);
}
int Memoized(vector<int> p, vector<int> &res, int n) {
if (res
>= 0)
return res
;
if (n == 0)
res[0] = 0;
else {
int i, temp;
temp = -1;
for (i = 1; i <= n; i ++)
temp = max(temp, p[i] + Memorized(p, res, n - i));
res
= temp;
}
return res
;
}


自底向上的CutRod的解法:

int BottomUpCutRod(vector<int> price, int n) {
/* res保存求出的更小的子问题的解 */
vector<int> res(n + 1);
res[0] = 0;
int i, j, temp;
for (i = 1; i <= n; i ++) {
temp = -1;
for (j = 1; j <= i; j ++)
temp = max(temp, price[j] + res[i - j]);
res[i] = temp;
}
return res
;
}


自底向上的解法采用子问题的自然顺序,如果j < i,则规模j的子问题比规模i的子问题更小,子问题j必须已经求出,因此,依次顺序求出规模i = 0、1、2、3……n的子问题。

1.3 求最优切割方案的动态规划解法

如果只是求钢条切割公司收益最大值,则到此就可以了;如果是求最优的切割方案,则还需要执行第4步。不仅需要求出保存最大收益值的res[i],同时需要求出最优解对应的第一段钢条的切割长度s[i]。

vector<int> ExtendBottomUpCutRod(vector<int> p, int n, vector<int> &res) {
vector<int> s(n + 1);
res[0];
int i, j;
for (i = 1; i <= n; i ++) {
int temp = -1;
for (j = 1; j <= i; j ++) {
if (p[j] + res[i - j] > temp) {
temp = p[j] + res[i - j];
s[i] = j;
}
res[i] = temp;
}
return s;
}
void Print(vector<int> res, vector<int> s, int n) {
cout << "max profit:" << res
<< endl;
while (n > 0) {
cout << s
;
n = n - s
;
}
cout << endl;
}


代码经过运行,输出结果正确。

参考:

[1]《算法导论》
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息