分类目录:《算法设计与分析》总目录
相关文章:
· 动态规划(一):基础知识
· 动态规划(二):钢条切割
· 动态规划(三):矩阵链乘法
· 动态规划(四):动态规划详解
· 动态规划(五):最长公共子序列


我们第一个应用动态规划的例子是求解一个如何切割钢条的简单问题。公司购买长钢条,将其切割为短钢条出售。切割工序本身没有成本支出。公司管理层希望知道最佳的切割方案。

假定我们知道公司出售一段长度为iii英寸的钢条的价格为ppp。钢条的长度均为整英寸。下图给出了一个价格表的样例。

每段长度为iii英寸的钢条为公司带来ppp美元的收益钢条切割问题是这样的:给定一段长度为nnn英寸的钢条和一个价格表ppp,求切割钢条方案,使得销售收益rrr最大。注意,如果长度为nnn英寸的钢条的价格ppp足够大,最优解可能就是完全不需要切割。

先考虑n=4n=4n=4的情况,下图给出了4英寸钢条所有可能的切割方案,包括根本不切割的方案。我们发现,将一段长度为4英寸的钢条切割为两段各长2英寸的钢条,将产生的收益p2+p2=5+5=10p_2+p_2=5+5=10p2​+p2​=5+5=10,为最优解。

上图给出了4英寸钢条的8种切割方案。根据价格表,在每段钢条之上标记了它的价格。最优策略为方案(c)——将钢条切割为两段长度均为2英寸的钢条——总价值为10。

长度为nnn英寸的钢条共有2n−12^{n-1}2n−1种不同的切割方案,因为在距离钢条左端iii英寸处,我们总是可以选择切割或不切割。我们用普通的加法符号表示切割方案,因此7=2+2+3表示将长度为7英寸的钢条切割为三段——两段长度为2英寸、一段长度为3英寸。

如果一个最优解将钢条切割为kkk段(1≤k≤n1≤k≤n1≤k≤n),那么最优切割方案n=i1+i2+⋯+ikn=i_1+i_2+\cdots+i_kn=i1​+i2​+⋯+ik​将钢条切割为长度分别为i1i_1i1​、i2i_2i2​、⋯\cdots⋯、iki_kik​的小段,得到最大收益rn=pi1+pi2+⋯+pikr_n=p_{i_1}+p_{i_2}+\cdots+p_{i_k}rn​=pi1​​+pi2​​+⋯+pik​​。

对于上述价格表样例,我们可以观察所有最优收益值rir_iri​及对应的最优切割方案:

更一般地,对于rir_iri​,我们可以用更短的钢条的最优切割收益来描述它:
rn=max(pn,r1+rn−1,r2+rn−2,⋯,rn−1+r1)r_n=max(p_n,r_1+r_{n-1},r_2+r_{n-2},\cdots,r_{n-1}+r_1)rn​=max(pn​,r1​+rn−1​,r2​+rn−2​,⋯,rn−1​+r1​)

第一个参数pnp_npn​对应不切割,直接出售长度为nnn英寸的钢条的方案。其他n−1n-1n−1个参数对应另外n−1n-1n−1种方案:对每个iii,首先将钢条切割为长度为iii和n−in-in−i的两段,接着求解这两段的最优切割收益,由于无法预知哪种方案会获得最优收益,我们必须考察所有可能的iii,选取其中收益最大者。如果直接出售原钢条会获得最大收益,我们当然可以选择不做任何切割。

注意到,为了求解规模为nnn的原问题,我们先求解形式完全一样,但规模更小的子问题。即当完成首次切割后,我们将两段钢条看成两个独立的钢条切割问题实例。我们通过组合两个相关子问题的最优解,并在所有可能的两段切割方案中选取组合收益最大者,构成原问题的最优解。我们称钢条切割问题满足最优子结构性质:问题的最优解由相关子问题的最优解组合而成,而这些子问题可以独立求解。

除了上述求解方法外,钢条切割问题还存在一种相似的但更为简单的递归求解方法:我们将钢条从左边切割下长度为iii的一段,只对右边剩下的长度为n−in-in−i的一段继续进行切割(递归求解),对左边的一段则不再进行切割。即问题分解的方式为:将长度为n的钢条分解为左边开始一段,以及剩余部分继续分解的结果。这样,不做任何切割的方案就可以描述为:第一段的长度为nnn,收益为pnp_npn​,剩余部分长度为0,对应的收益为r0=0r_0=0r0​=0。于是我们可以得到钢条切割的简化版本:
rn=max⁡1≤i≤n(pi+rn−1)r_n=\max \limits_{1 \leq i \leq n}(p_i+r_{n-1})rn​=1≤i≤nmax​(pi​+rn−1​)

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

def cut_rod(p, n):if n == 0:return 0r = -1for i in range(n):r = max(r, p[i] + cut_rod(p, n - i - 1))return r

过程cut_rod(p, n)以价格数组p[1..n]和整数n为输入,返回长度为n的钢条的最大收益。若n=0,不可能有任何收益,所以cut_rod(p, n)的第2~3行返回0。第4行将最大收益r初始化为−1-1−1,以便第5~6行的for循环能正确计算,第7行返回计算结果。

在你运行cut_rod(p, n)的时候你可能会发现,一旦输入规模稍微变大,程序运行时间会变得相当长。每当将nnn增大1,程序运行时间差不多就会增加1倍。其原因在于,cut_rod(p, n)反复地用相同的参数值对自身进行递归调用,即它反复求解相同的子问题。下图显示了n=4n=4n=4时的调用过程:

为了分析cut_rod(p, n)的运行时间,令T(n)T(n)T(n)表示第二个参数值为nnn时cut_rod(p, n)的调用次数。

此值等于递归调用树中根为nnn的子树中的结点总数,注意,此值包含了根结点对应的最初的一次调用。因此T(0)=1T(0)=1T(0)=1,且

T(n)=1+∑i=0n−1T(i)=2nT(n)=1+\sum_{i=0}^{n-1}T(i)=2^nT(n)=1+i=0∑n−1​T(i)=2n

回过头看,cut_rod(p, n)的指数运行时间并不令人惊讶。对于长度为nnn的钢条,cut_rod(p, n)显然考察了所有2n−12^{n-1}2n−1种可能的切割方案。递归调用树中共有2n−12^{n-1}2n−1个叶结点,每个叶结点对应一种可能的钢条切割方案。对每条从根到叶的路径,路径上的标号给出了每次切割前右边剩余部分的长度(子问题的规模)。

使用动态规划方法求解最优钢条切割问题

我们现在展示如何将cut_rod(p, n)转换为一个更高效的动态规划算法。

我们已经看到,朴素递归算法之所以效率很低,是因为它反复求解相同的子问题。因此,动态规划方法仔细安排求解顺序,对每个子问题只求解一次,并将结果保存下来。如果随后再次需要此子问题的解,只需查找保存的结果,而不必重新计算。因此,动态规划方法是付出额外的内存空间来节省计算时间,是典型的时空权衡的例子。而时间上的节省可能是非常巨大的:可能将一个指数时间的解转化为一个多项式时间的解。如果子问题的数量是输入规模的多项式函数,而我们可以在多项式时间内求解出每个子问题,那么动态规划方法的总运行时间就是多项式阶的。

动态规划有两种等价的实现方法,下面以钢条切割问题为例展示这两种方法。

第一种方法称为带备忘的自顶向下法。此方法仍按自然的递归形式编写过程,但过程会保存每个子问题的解(通常保存在一个数组或散列表中)。当需要一个子问题的解时,过程首先检查是否已经保存过此解。如果是,则直接返回保存的值,从而节省了计算时间;否则,按通常方式计算这个子问题。我们称这个递归过程是带备忘的,因为它“记住”了之前已经计算出的结果。

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

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

def cut_rod(p, n):r = [-1] * nreturn cut_rod_dp(p, n, r)def cut_rod_dp(p, n, r):if r[n - 1] >= 0:return r[n - 1]if n == 0:q = 0else:q = -1for i in range(1, n + 1):q = max(q, p[i - 1] + cut_rod_dp(p, n - i, r))r[n - 1] = qreturn q

这里,主过程cut_rod(p, n)将辅助数组r的元素均初始化为-1,这是一种常见的表示“未知值”的方法(已知的收益总是非负值)。然后它会调用辅助过程cut_rod_dp(p, n, r)。过程cut_rod_dp(p, n, r)是最初的cut_rod(p, n)引入备忘机制的版本。它首先检查所需值是否已知,如果是,则第2行直接返回保存的值;否则,第3~7行用通常方法计算所需值q,第8行将q存入r,第9行将其返回。

自底向上版本更为简单:

def cut_rod(p, n):r = [-1] * nfor i in range(n):q = p[i]for j in range(i):q = max(q, p[j] + p[i - j - 1])r[i] = qreturn r[n - 1]

自底向上版本cut_rod(p, n)采用子问题的自然顺序:若i<ji<ji<j,则规模为iii的子问题比规模为jjj的子问题“更小”。因此,过程依次求解规模为j=0,1,⋯,nj=0, 1, \cdots, nj=0,1,⋯,n的子问题。

子问题图

当思考一个动态规划问题时,我们应该弄清所涉及的子问题及子问题之间的依赖关系。问题的子问题图准确地表达了这些信息。下图显示了n=4n=4n=4时钢条切割问题的子问题图。它是一个有向图,每个顶点唯一地对应一个子问题。若求子问题xxx的最优解时需要直接用到子问题yyy的最优解,那么在子问题图中就会有一条从子问题xxx的顶点到子问题yyy的顶点的有向边。例如,如果自顶向下过程在求解xxx时需要直接递归调用自身来求解yyy,那么子问题图就包含从xxx到yyy的一条有向边。我们可以将子问题图看做自顶向下递归调用树的“简化版”或“收缩版”,因为树中所有对应相同子问题的结点合并为图中的单一顶点,相关的所有边都从父结点指向子结点。

自底向上的动态规划方法处理子问题图中顶点的顺序为:对于一个给定的子问题xxx,在求解它之前求解邻接至它的子问题yyy。自底向上动态规划算法是按“逆拓扑序”或“反序的拓扑序”来处理子问题图中的顶点。换句话说,对于任何子问题,直至它依赖的所有子问题均已求解完成,才会求解它。类似地,我们可以用“深度优先搜索”来描述自顶向下动态规划算法处理子问题图的顺序。

子问题图G=(V,E)G=(V, E)G=(V,E)的规模可以帮助我们确定动态规划算法的运行时间。由于每个子问题只求解一次,因此算法运行时间等于每个子问题求解时间之和。通常,一个子问题的求解时间与子问题图中对应顶点的度(出射边的数目)成正比,而子问题的数目等于子问题图的顶点数。因此,通常情况下,动态规划算法的运行时间与顶点和边的数量呈线性关系。

重构解

前文给出的钢条切割问题的动态规划算法返回最优解的收益值,但并未返回解本身。我们可以扩展动态规划算法,使之对每个子问题不仅保存最优收益值,还保存对应的切割方案。利用这些信息,我们就能输出最优解:

def cut_rod(p, n):r = [-1] * ns = list(range(n))for i in range(n):q = p[i]for j in range(i):if q < p[j] + p[i - j - 1]:q =p[j] + p[i - j - 1]s[i] = jr[i] = qreturn r[n - 1], s[n - 1]

此过程与原过程很相似,差别只是创建了数组s,并在求解规模为iii的子问题时将第一段钢条的最优切割长度jjj保存在了s[i]中。

算法设计与分析——动态规划(二):钢条切割相关推荐

  1. 算法设计与分析-----动态规划

    算法设计与分析-----动态规划(c语言) 一.动态规划 1.定义 2.动态规划问题的解法 3.动态规划求解的基本步骤 4.动态规划与其他方法的比较 5.求解整数拆分问题 6.求解最大连续子序列和问题 ...

  2. 深大算法设计与分析实验二——分治法求最近点对问题

    源代码: 深大算法设计与分析实验二--分治法求最近点对问题代码-C/C++文档类资源-CSDN下载 目录 实验问题 一.实验目的: 二.内容: 三.算法思想提示 产生不重复的随机点算法: 蛮力算法: ...

  3. 算法设计与分析——动态规划(五):最长公共子序列

    分类目录:<算法设计与分析>总目录 相关文章: · 动态规划(一):基础知识 · 动态规划(二):钢条切割 · 动态规划(三):矩阵链乘法 · 动态规划(四):动态规划详解 · 动态规划( ...

  4. 算法导论15.1动态规划之钢条切割

    动态规划与钢条切割 1.分治算法与动态规划 相同点: 都是通过组合子问题的解来求解原问题 不同: 1.分治将问题划分为互不相交的子问题,递归地求解子问题,在将它们的解组合起来,求出原问题. 2.动态规 ...

  5. 计算机算法设计与分析 动态规划 实验报告,动态规划法解最长公共子序列(计算机算法设计与分析实验报告).doc...

    动态规划法解最长公共子序列(计算机算法设计与分析实验报告) 实报 告 实验名称:任课教师::姓 名:完成日期:二.主要实验内容及要求: 要求按动态规划法原理求解问题: 要求交互输入两个序列数据: 要求 ...

  6. 《算法导论》中动态规划求解钢条切割问题

    动态规划算法概述 动态规划(dynamic programming)1是一种与分治方法很像的方法,都是通过组合子问题的解来求解原问题.不同之处在于,动态规划用于子问题重叠的情况,比如我们学过的斐波那契 ...

  7. 算法设计与分析 实验二 分治法求解最近点对问题

    分治法求解最近点对问题 一.实验目的与要求 1.实验基本要求 2.实验亮点 二.实验内容与方法 三.实验步骤与过程 (一)一些准备工作 1.实验流程 2.数据生成与去除重复点 (二)暴力穷举法 1.算 ...

  8. 算法设计与分析——动态规划(一)矩阵连乘

    动态规划--Dynamic programming,可以说是本人一直没有啃下的骨头,这次我就得好好来学学Dynamic programming. OK,出发! 动态规划通常是分治算法的一种特殊情况,它 ...

  9. 算法设计与分析——动态规划——数字三角形问题

    数字三角形问题 1.题目描述:给定一个由n行数字组成的数字三角形,如图3-7所示.设计一个算法,计算出从三角形的顶至底的一条路径,使该路径经过的数字总和最大. 算法设计:对于给定的由n行数字组成的数字 ...

最新文章

  1. python数组切片教程_手把手numpy教程【二】——数组与切片
  2. pt-online-schema-change 修改主键导致数据删除失败的问题调查
  3. byte数组转blob类型_Java类型相互转换byte[]类型,blob类型
  4. C语言国二上机题库,【高分飘过】2013年国二C语言上机题库(必备完美版).doc
  5. 数学建模——典型相关分析及相关SPSS操作
  6. 机器学习中的数学——概率论基础知识
  7. 【Unity面试】 Unity基础核心 | 面试真题 | 全面总结 | 建议收藏
  8. 公路堵车概率模型:Nagel-Schreckenberg 模型模拟
  9. BMVC 2020 开幕,196篇论文73篇开源~
  10. NetBeans ide操作流程及注意事项
  11. 手把手教你接入微信开放平台,实现网站拉起微信账号登录,从0开始详细记录
  12. 鼎捷APS助力茶花家居实现智能高效生产排程
  13. java web 常见框架
  14. ZZULIOJ 1800 少水群多刷题
  15. app模式会被第三方平台模式取代吗_未来APP将取代移动网站? - 搜外问答
  16. entity、model、domain三个包名的意思
  17. Android手机多媒体
  18. MySQL管理常用工具介绍
  19. 视频融合平台EasyCVR接入海康SDK时无法播放录像该如何解决?
  20. IDEA查询包含关键字的文本文件

热门文章

  1. layui修改表格行高
  2. Java 8中 直接通过List进行分组求和
  3. 敏捷物联——引领生产和服务创新
  4. 计算机组成原理笔记(王道考研) 第六章:总线
  5. 实现轮播模拟点击事件
  6. 文旅灯光秀互动应用有什么优势
  7. 初学盲打,免费,免安装,高颜值的在线打字练习网站
  8. Appscan安全测试
  9. openjudge666:放苹果
  10. LabVIEW编程LabVIEW开发 旧程序升级维护