算法导论(第15章 动态规划)*
第15章 动态规划
动态规划(dynamic programming)与分治方法相似,都是通过组合子问题的解来求解原问题(在这里,“programming”指的是一种表格法,并非编写计算机程序)。
分治方法将问题划分为互不相交的子问题,递归地求解子问题,再将它们的解组合起来,求出原问题的解。
动态规划应用于子问题重叠的情况,即不同的子问题具有公共的子子问题(子问题的求解是递归进行的,将其划分为更小的子子问题)。在这种情况下,分治算法会做许多不必要的工作,它会反复求解那些公共子子问题。而动态规划算法对每个子子问题只求解一次,将其解保存在一个表格中,从而避免每次求解一个子子问题时都重新计算。
动态规划方法通常用来求解最优化问题(optimization problem)。这类问题可以有很多可行解,每个解都有一个值,我们希望寻找具有最优值的解。我们称这样的解为问题的一个最优解(an optimal solution),而不是最优解(the optimal solution),因为可能有多个解都达到最优值。
通常按如下四个步骤来设计一个动态规划算法:
- 刻画一个最优解的结构特征。
- 递归地定义最优解的值。
- 计算最优解的值,通常采用自底向上的方法。
- (回溯)利用计算出的信息构造一个最优解。——如果仅仅需要一个最优解的值,而非解本身,则不需要此步骤
15.1 钢条切割
对于长度为
对于切割长度为
第一个参数
对应不切割直接出售长度为 英寸的钢条的方案,其他 个参数对应另外 种方案:对每个 ,首先将钢条切割为长度为 和 的两段,接着求解这两段的最优切割收益 和 ——此时我们必须考察所有可能的 ,选取其中最大收益者。
注意到,为了求解规模为n的原问题,我们先求解形式完全一样,但规模更小的子问题。我们称钢条切割问题满足最优子结构(optimal substructure)性质:问题的最优解由相关问题的最优解组合而成,而这些子问题可以独立求解。
我们可以将问题分解的方式简化为:将长度为
自顶向下递归实现
CUT-ROD(p, n)
1 if n == 0
2 return 0
3 q = -∞
4 for i = 1 to n
5 q = max(q, p[i] + CUT-ROD(p, n - i))
6 return q
此时,一旦输入规模稍微变大,程序运行时间会变得相当长。原因在于CUT-ROD反复地用相同的参数值对自身进行递归调用——它反复求解相同的子问题。
使用动态规划方法求解最优钢条切割问题
动态规划方法仔细安排求解顺序,对每个子问题只求解一次,并将结果保存下来。如果随后再次需要此子问题的解,只需查找保存的结果,而不必重新计算。
动态规划付出额外的内存空间来节省计算时间,是典型的时空权衡(time-memory trade-off)的例子。
动态规划有两种等价的实现方法:
- 带备忘的自顶向下法(top-down with memoization)。此方法仍按自然的递归形式编写过程,但过程会保存每个子问题的解(通常保存在一个数组或散列表中)。当需要一个子问题的解时,过程首先检查是否以及保存过此解,如果是,则之间返回保存的值,从而节省了计算时间——我们称这个递归过程是带备忘的(memoized)。
MEMOIZED-CUT-ROD(p, n)
1 let r[0..n] be a new array
2 for i = 0 to n
3 r[i] = -∞
4 return MEMOIZED-CUT-ROD-AUX(p, n, r)
MEMOIZED-CUT-ROD-AUX(p, n, r)
1 if r[n] >= 0
2 return r[n]
3 if n == 0
4 q = 0
5 else q = -∞
6 for i = 1 to n
7 q = max(q, p[i] + MEMOIZED-CUT-ROD-AUX(p, n - i, r))
8 r[n] = q
9 return q
- 自底向上法(bottom-up method)。这种方法一般需要恰当定义子问题“规模”的概念,使得任何子问题的求解都只依赖于“更小的”子问题的求解——将子问题按规模排序,按由小到大的顺序进行求解,当求解某个子问题时,它所依赖的那些更小的子问题都已求解完毕,结果已经保存——每个子问题只需求解一次,当我们求解它时,它的所有前提子问题都已求解完成。
BOTTOM-UP-CUT-ROD(p, n)
1 let r[0..n] be a new array
2 r[o] = 0
3 for j = 1 to n
4 q = -∞
5 for i = 1 to j
6 q = max(q, p[i] + r[j - i])
7 r[j] = q
8 return r[n]
两种方法得到的算法具有相同的渐近运行时间(由于没有频繁的递归函数调用的开销,自底向上方法的时间复杂性函数通常具有更小的系数)。仅有的差异是在某些特殊情况下,自顶向下方法并未真正递归地考察所有可能的子问题。
子问题图
重构解
15.2 矩阵链乘法
矩阵链乘法问题(matrix-chain multiplication problem)可描述如下:给定
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步