动态规划(Dynamic Programming)通常用于解决最优化问题,可采用动态规划方法求解的问题需要具备以下两个特性:最优子结构(Optimal Substructure)和子问题重叠(Overlapping Subproblems)。
如果一个问题可以分解成若干个子问题,若原问题的最优解由其子问题的最优解组合而成,并且这些子问题可以独立求解,则该问题具有最优子结构特性;若子问题之间存在重叠的子问题,则该问题具有子问题重叠特性。
回顾 Fibonacci 数列求解问题, f i b ( n ) \mathrm{fib}(n)~ fib(n) 的值由 f i b ( n − 2 ) ~\mathrm{fib}(n-2)~ fib(n−2) 和 f i b ( n − 1 ) ~\mathrm{fib}(n-1)~ fib(n−1) 的值相加得到,并且子问题 f i b ( n − 2 ) ~\mathrm{fib}(n-2)~ fib(n−2) 和子问题 f i b ( n − 1 ) ~\mathrm{fib}(n-1)~ fib(n−1) 可以独立求解,所以 ~ Fibonacci 数列求解问题具有最优子结构特性;求解 f i b ( 4 ) ~\mathrm{fib}(4)~ fib(4) 的二叉树图示中,左子树中的子问题 f i b ( 2 ) ~\mathrm{fib}(2) fib(2) 与右子树中的子问题 f i b ( 2 ) ~\mathrm{fib}(2)~ fib(2) 相同,所以 ~ Fibonacci ~ 数列求解问题具有子问题重叠特性。因为 ~ Fibonacci ~ 数列求解问题同时具有最优子结构和子问题重叠特性,所以 ~ Fibonacci ~ 数列求解问题可以采用动态规划来求解。
由动态规划的最优子结构和子问题重叠这两个特性可以看出,动态规划的实质就是在采用分治策略的同时避免重叠子问题的冗余计算。动态规划将原问题分解成可独立求解的子问题,计算过程中存储子问题的解,避免重复计算相同的子问题。动态规划一般由两种方法来实现,一种为自顶向下的备忘录方式,用递归实现,一种为自底向上的方式,用迭代实现。
遇到一个问题,在判断这个问题是否可用动态规划求解时,一般采用自顶向下的分析思路。
一般地,分析一个问题是否可以采用动态规划求解有以下步骤:
(1) 采用自顶向下的思路,分析最优解的结构特征,分析问题是否存在最优子结构性质。
(2) 分析子问题之间的关联,分析问题是否存在重叠子问题。
如果一个问题同时具有最优子结构和重叠子问题这两个特征,则这个问题可以采用动态规划求解。在实际应用中,先采用自顶向下的直观思路给出解决问题的递归式,然后再采用自底向上的思路完成动态规划算法的设计。
设计一个动态规划算法通常包含 3 ~3~ 3 个步骤:
(1) 通过自顶向下的分析,用递归的形式定义一个最优解\footnote{注意可能存在多个最优解。}。
(2) 探讨底层的边界问题。
(3) 采用自底向上的方法,根据最优解的形式设计动态规划算法。
下面通过例题深入理解动态规划的实际应用。
例题: 小明准备周六勤工俭学,他在培训机构找到了一些家教兼职信息,培训机构提供了 8 ~8~ 8 个家教任务可供选择。家教任务的报酬为 1 ~1~ 1 至 8 ~8~ 8 百元,价格不等,持续时间不一样。小明按照任务结束时间早晚对 8 ~8~ 8 个任务进行了排序并编号,任务结束时间最早的编号为 1 ~1 1,结束最晚的编号为 8 ~8 8,排序编号结果如下图 ~ \ref{fig_dpExample} 所示,图中任务条中间数值为该家教任务的相应报酬,单位为百元。请问小明应该选择哪些任务才能使周六一天获得的报酬最大?
\Solution 该例题是一个最优化问题,可以考虑是否用动态规划求解,采用动态规划求解的分析步骤。
(1) 采用自顶向下的思路,分析最优解的结构特征,分析问题是否存在最优子结构性质。
从任务 8 ~8~ 8 开始分析,将此时的最大化报酬的策略记为 m a x V ( 8 ) ~\mathrm{maxV}(8) maxV(8),针对任务 8 ~8 8