动态规划 钢条切割
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]《算法导论》
相关文章推荐