1-4、算法设计常用思想之动态规划法
文章内容来自王晓华老师
动态规划(Dynamic Programming)是解决多阶段决策问题常用的最优化理论,动态规划和分治法一样,也是通过定义子问题,先求解子问题,然后在由子问题的解组合出原问题的解。
但是它们之间的不同点是分治法的子问题之间是相互独立的,而动态规划的子问题之间存在堆叠关系(递推关系式确定的递推关系)。
动态规划方法的原理就是把多阶段决策过程转化为一系列的单阶段决策问题,利用各个阶段之间的递推关系,逐个确定每个阶段的最优化决策,最终堆叠出多阶段决策的最优化决策结果。
动态规划问题有很多模型,常见的有线性模型、(字符或数字)串模型、区间模型、状态压缩模型,等等,本节课后面介绍的最长公共子序列问题,就是一个典型的串模型。
动态规划适合求解多阶段(状态转换)决策问题的最优解,也可用于含有线性或非线性递推关系的最优解问题,但是这些问题都必须满足最优化原理和子问题的“无后向性”。
• 最优化原理:最优化原理其实就是问题的最优子结构的性质,如果一个问题的最优子结构是不论过去状态和决策如何,对前面的决策所形成的状态而言,其后的决策必须构成最优策略。
也就是说,不管之前的决策是否是最优决策,都必须保证从现在开始的决策是在之前决策基础上的最优决策,则这样的最优子结构就符合最优化原理。
• 无后向性(无后效性):所谓“无后向性”,就是当各个阶段的子问题确定以后,对于某个特定阶段的子问题来说,它之前各个阶段的子问题的决策只影响该阶段的决策,对该阶段之后的决策不产生影响。
对于某一个状态 S 来说,只要 S 状态确定了以后,S 以后的那些依靠 S 状态做最优选择的状态也就都确定了,S 之后的状态只受 S 状态的影响。
也就是说,无论之前是经过何种决策途径来到了 S 状态,S 状态确定以后,其后续状态的演化结果都是一样的,不会因为到达 S 状态的决策路径的不同而产生不同的结果,这就是无后向性。
动态规划的基本思想
动态规划分解子问题不是简单地按照“大事化小”的方式进行的,而是沿着决策的阶段来划分子问题,决策的阶段可以随时间划分,也可以随着问题的演化状态来划分。
动态规划法的子问题不是互相独立的,子问题之间通常有包含关系,甚至两个子问题可以包含相同的子子问题
使用动态规划法一般需要四个步骤,分别是:
1. 定义最优子问题(最优解的子结构)
2. 定义状态(最优解的值)
3. 定义决策和状态转换方程(定义计算最优解的值的方法)
4. 确定边界条件
最长公共子序列(LCS)问题
最长公共子序列(LCS,Longest Common Subsequence)的定义是:一个序列 S,如果分别是两个或多个已知序列的子序列,且是符合此条件的子序列中最长的,则称 S 为已知序列的最长公共子序列。
DpLcs() 函数是用动态规划法解决最长公共子序列问题的算法实现,从下面的代码中可以看出,两个 for 循环实际上是做了个广域搜索,将所有子序列的结果都求出来了,只有 s[m,n] 代表的是原始问题的最长公共子序列长度。
int DpLcs(const std::string& str1, const std::string& str2, int s[MAX_STRING_LEN][MAX_STRING_LEN]) { std::string::size_type i,j; for(i = 1; i <= str1.length(); i++) s[i][0] = 0; for(j = 1; j <= str2.length(); j++) s[0][j] = 0; for(i = 1; i <= str1.length(); i++) { for(j = 1; j <= str2.length(); j++) { if((str1[i - 1] == str2[j - 1])) { s[i][j] = s[i - 1][j - 1] + 1; } else { s[i][j] = std::max(s[i - 1][j], s[i][j - 1]); } } } return s[str1.length()][str2.length()]; }
lua实现 还未测试验证
function DpLcs(str1, str2) local s = {} local arr_str1 = {} local arr_str2 = {} for i = 1, #str1 do s[i] = s[i] or {} s[i][0] = 0; for j = 1, #str2 do s[0] = s[0] or {} s[0][j] = 0; end for i = 1, #str1 do for j = 1, #str2 do if(str1[i - 1] == str2[j - 1]) then s[i][j] = s[i - 1][j - 1] + 1; else s[i][j] = math.max(s[i - 1][j], s[i][j - 1]); end end end return s[#str1][#str2] end