0%

动态规划

简介

动态规划是解决多阶段决策问题常用的最优化理论,该理论由美国数学家 Bellman 等人在 1957 年提出,用于研究多阶段决策过程的优化问题, 原理是把多阶段决策过程转化为一系列的单阶段决策问题,利用各个阶段之间的递推关系,逐个确定每个阶段的最优化决策,最终堆叠出多阶段决策的最优化决策结果

应用动态规划解题的效率,取决于问题的类型,并不是任何情况下都有最高的效率, 比如在很多 NP 问题中,动态规划法就不是一种多项式时间的方法,而是一种穷举,其效率就是指数时间复杂度

动态规划适合求解多阶段(状态转换)决策问题的最优解,也可用于含有线性或非线性递推关系的最优解问题,但是这些问题都必须满足最优化原理和子问题的 “无后向性”

  • 最优化原理:不管之前决策是否是最优决策,都必须保证从现在开始的决策是在之前决策基础上的最优决策,则这样的最优子结构就符合最优化原理
  • 无后向性(无后效性):当各个阶段的子问题确定以后,对于某个特定阶段的子问题来说,它之前的各个阶段的子问题的决策只影响该阶段的决策,对该阶段之后的决策不产生影响,也就是说,每个阶段的决策仅受之前决策的影响,但是不影响之后各阶段的决策

基本思想

与分治法区别

  • 相同点: 对问题进行分解,通过求解小规模的子问题再反推出原问题的结果
  • 不同点:
    • 分治法要求子问题是互相独立的,以便分别求解并最终合并出原始问题的解
    • 分治法有固定的算法实现模式
    • 动态规划法的子问题不是互相独立的,子问题之间通常有包含关系,甚至两个子问题可以包含相同的子子问题
    • 动态规划法沿着决策的阶段划分子问题,决策的阶段可以随时间划分,也可以随着问题的演化状态划分
    • 动态规划法作为解决多阶段决策最优化问题的一种思想,它没有具体的实现模式,可以用带备忘录的递归方法实现,也可以根据堆叠子问题之间的递推公式用递推的方法实现

步骤

定义最优子问题

确定问题的优化目标以及如何决策最优解,并对决策过程划分阶段, 也就是划分一个问题从开始到解决需要经过的环节, 这些环节前后关联, 据问题的结构,可以按照时间顺序划分阶段,也可以按照问题的演化状态划分阶段, 这样对问题的解就转换为按照阶段顺序依次选择的一个最优化决策序列

  • 装配站问题, 把工件从一个装配站移到下一个装配站就可以看作是一个阶段,其子问题就可以定义为从一个装配站转移到下一个装配站,直到最后一个装配站完成工件组装
  • 对于背包问题,每选择装一个物品就可以看作一个阶段,其子问题就可以定义为每次向包中装一个物品,直到超过背包的最大容量为止
  • 最长公共子序列问题可以按照问题的演化状态划分阶段,这需要首先定义状态,有了状态的定义,只要状态发生了变化,就可以认为是一个阶段

定义状态

状态既是决策的对象,也是决策的结果,对于每个阶段来说,对起始状态施加决策,使得状态发生改变,得到决策的结果状态, 初始状态经过每一个阶段的决策(状态改变)之后,最终得到的状态就是问题的解。当然,不是所有的决策序列施加于初始状态后都可以得到最优解,只有一个决策序列能得到最优解。状态的定义是建立在子问题定义的基础上的,因此状态必须满足“无后向性”要求。必要时,可以增加状态的维度,引入更多的约束条件,使得状态定义满足“无后向性”要求

  • 装配站问题的实质就是在不同的装配线之间选择装配站,使得工件装配完成的时间最短,其状态 $s [ i , j ]$ 就可以定义为通过第 i 条装配线的第 j 个装配站所需要的最短时间
  • 背包问题本身是个线性过程,但是如果简单将状态定义为装入的物品编号,也就是定义 $s [ i ]$ 为装入第 i 件物品后获得的最大价值,则子问题无法满足“无后向性”要求,原因是之前的任何一个决策都会影响到所有的后序决策(因为装入物品后背包容量发生了变化),因此需要增加一个维度的约束。考虑到每装入一个物品,背包的剩余容量就会减少,故而选择将背包容量也包含的状态定义中。最终背包问题的状态 $s [ i , j ]$ 定义为将第 i 件物品装入容量为 j 的背包中所能获得的最大价值
  • 对于最长公共子序列问题,如果定义 $str1[1 \dots i ]$ 为第一个字符串前 i 个字符组成的子串,定义 $str2[1 \dots j ]$ 为第二个字符串的前 j 个字符组成的子串,则最长公共子序列问题的状态 $s [ i , j ]$ 定义为 $str1[1 \dots i ]$ 与 $str2[1 \dots j ]$ 的最长公共子序列长度

定义决策和状态转换方程

  • 决策: 使状态发生转变的选择动作,如果选择动作有多个,则决策就是取其中能使得阶段结果最优的那一个
  • 状态转换方程: 状态转换关系的一系列等式,也就是从 $n -1$ 阶段到 n 阶段演化的规律

状态转换取决于子问题的堆叠方式,如果状态定义得不合适,就会导致子问题之间没有重叠,也就不存在状态转换关系了。没有状态转换关系,动态规划也就没有意义了,实际算法就退化为像分治法那样的朴素递归搜索算法

装配站问题的决策就是选择在当前工作线上的下一个工作站继续装配,或者花费一定的开销将其转移到另一条工作线上的下一个工作站继续装配

如果定义 $a [ i , j ]$ 为第 i 条工作线的第 j 个装配站需要的装配时间,$k [ i , j ]$ 为从另一条工作线转移到第 i 条装配线的第 j 个装配站需要的转移开销,则装配站问题的状态转换方程可以描述为

$$
s[1, j] = min(s[1, j - 1] + a[1, j], s[2, j - 1] + k[1, j] + a[1, j]) \\
s[2, j] = min(s[2, j - 1] + a[2, j], s[1, j - 1] + k[2, j] + a[2, j])
$$

背包问题的决策就是判断装入第 i 件物品获得的收益最大还是不装入第 i 件物品获得的收益最大

如果不装入第 i 件物品,则背包内物品的价值仍然是 $s [ i -1, j ]$ 状态,如果装入第 i 件物品,则背包内物品的价值就变成 $s [ i , j - V_i ]+ P_i$ 状态,其中 $V_i$ 和 $P_i$ 分别是第 i 件物品的容积和价值,决策的状态转换方程就是

$$
s[i, j] = max(s[i - 1, j], s[i, j - V_i] + P_i)
$$

最长公共子序列问题的决策方式就是判断 $str1[ i ]$ 和 $str2[ j ]$ 的关系

如果 $str1[ i ]$ 与 $str2[ j ]$ 相同,则公共子序列的长度应该是 $s[ i -1,j -1]+1$ ,否则就分别尝试匹配 $str1[1 \dots i -1]$ 与 $str2[1 \dots j ]$ 的最长公共子串,以及 $str1[1 \dots i ]$ 与 $str2[1 \dots j -1]$ 的最长公共子串,然后取二者中较大的那个值作为 $s [ i , j ]$ 的值。最长公共子序列问题的状态转换方程就是

$str1[ i ]$ 与 $str2[ j ]$ 相同

$$
s[i, j] = s[i - 1, j - 1] + 1
$$

$str1[ i ]$ 与 $str2[ j ]$ 不相同

$$
s[i, j] = max(s[i, j - 1], s[i - 1, j])
$$

确定边界条件

  • 递归加备忘录方式(记忆搜索)实现的动态规划方法,边界条件实际上就是递归终结条件,无需额外的计算
  • 对于使用递推关系直接实现的动态规划方法,需要确定状态转换方程的递推式的初始条件或边界条件,否则无法开始递推计算

对于装配站问题,初始条件就是工件通过第一个装配站的时间,对于两条装配线来说,工件通过第一个装配站的时间虽然不相同,但是都是确定的值,就是移入装配线的开销加上第一个装配站的装配时间

装配站问题的边界条件就是

$$
s[1, 1] = k[1, 1] + a[1, 1] \\
s[2, 1] = k[2, 2] + a[2, 2]
$$

背包问题的边界条件就是没有装入任何物品的状态

$$
s[0, V_max] = 0
$$

确定最长公共子序列问题的边界条件,要从其决策方式入手,当两个字符串中的一个长度为 0 的时候,其公共子序列长度肯定是 0,因此其边界条件就是

$$
s[i, j] = 0 \\
i = 0 \parallel j = 0
$$

例子