一、学习内容
第三章主要学习了动态规划算法的相关内容,思维导图如下:
1. 动态规划算法原理、步骤及要素
动态规划与分治法类似,都是把大问题拆分成小问题,通过寻找大问题与小问题的递推关系,解决一个个小问题,最终达到解决原问题的效果。但不同的是,分治法在子问题和子子问题等上被重复计算了很多次,而动态规划则具有记忆性,通过填写表把所有已经解决的子问题答案纪录下来,在新问题里需要用到的子问题可以直接提取,避免了重复计算,从而节约了时间,所以在问题满足最优性原理之后,用动态规划解决问题的核心就在于填表,表填写完毕,最优解也就找到。
最优性原理是动态规划的基础,最优性原理是指“多阶段决策过程的最优决策序列具有这样的性质:不论初始状态和初始决策如何,对于前面决策所造成的某一状态而言,其后各阶段的决策序列必须构成最优策略”。
步骤:
- 划分:按照问题的特征,把问题分为若干阶段。注意:划分后的阶段一定是有序的或者可排序的。
- 确定状态和状态变量:将问题发展到各个阶段时所处的各种不同的客观情况表现出来。状态的选择要满足无后续性。
- 确定决策并写出状态转移方程:状态转移就是根据上一阶段的决策和状态来导出本阶段的状态。根据相邻两个阶段状态之间的联系来确定决策方法和状态转移方程。
- 边界条件:状态转移方程是一个递推式,因此需要找到递推终止的条件。
基本要素:
- 最优子结构:当问题的最优解包含了其子问题的最优解时,称该问题具有最优子结构性质。问题的最优子结构性质提供了该问题可用动态规划算法求解的重要线索。在动态规划算法中,利用问题的最优子结构性质,以自底向上的方式递归地从子问题的最优解逐步构造出整个问题的最优解。
- 重叠子问题:可用动态规划算法求解的问题应具备的另一个基本要素是子问题的重叠性质。在用递归算法自顶向下求解问题时,每次产生的子问题并不总是新问题,有些子问题被反复计算多次。动态规划算法正是利用了这种子问题的重叠性质,对每一个子问题只解一次,而后将其解保存在一个表格中,当再次需要此子问题时,只要简单地用常数时间查看一下结果。通常,不同的子问题个数随问题的大小呈多项式增长。因此,用动态规划算法通常只需要多项式时间,从而获得较高的解题效率。
- 备忘录方法:就可以在递归处理问题的基础上,将需要后来多次计算的问题进行缓存,减少了重复子问题的计算。但是书中所记的备忘录方法没有真正的将自上而下的精髓体现出来,若是将自上而下的思想结合最优子结构的思想,则可以对问题进行修剪枝条,在宏观出即可去掉一大部分的不需计算的方面,比如一个问题的划分可以有两种,选择了最优的一种,就可以将另一种非最优情况下的所有计算均省去,然后再对第一次的划分再次进行划分,其结构是树由根向叶,不断的择取最优的树干,最终至叶子,非最优树干直接不计算。
2. 相关问题
- 矩阵连乘问题
- 最长公共子序列
- 0-1背包问题
- 最优二叉搜索树
二、算法设计与分析
1. 问题阐述
采用动态规划算法计算任意两个字符序列的最长公共子序列;总结实验出现问题及解决方法。
2. 设计算法
- 将问题分解为小的子问题。
- 假设两个字符串序列分别为:X={x0, x1, x2,..., xm},Y={y0, y1, y2,..., yn}。从后往前比较字符。Z={z0,z1,z2,z3,..., zk}是他们的任意公共子序列。
- 如果xm = yn,则zk = xm = yn 且 Zk-1是Xm-1和Yn-1的一个LCS
- 如果xm != yn 且 zk != xm,则Z是Xm-1和Y的一个LCS
- 如果xm != yn 且 zk != yn,则Z是X和Yn-1的一个LCS
- 所以如果用一个二维数组c表示字符串X和Y中对应的前i,前j个字符的LCS的长度(也就是截止到X的第i个字符和截止到Y的第j个字符的最长公共子序列长度)的话,可以得到以下公式:
3. 实验中出现问题
- 首先是我想要让lcs的长度矩阵c下标从0开始,省去置0的首行和首列,然后就遇到了一系列的小问题。大多都是不够仔细,索引值超出范围之类的错误“IndexError: index 5 is out of bounds for axis 0 with size 5”
- 将程序输出的矩阵和自己手算的矩阵对比之后发现程序中矩阵c的首行和首列值写错了,除c[0][0] = 0以外都要进行计算操作。
- 用来输出lcs使用的来源矩阵flag的元素原本是置为0、1、2的,但这样输出后不够直观,很难理解最长公共子字符串问题的观察矩阵输出结果的步骤。
4. 解决办法
- 程序报错后首先进行了单步调试,观察数值变化发现问题进行修改。
- 结果错误后对比矩阵发现逻辑问题,进行了完善修改。
- 将flag矩阵元素进行修改,lcs中字符使用'√',其他用'|'和'-'代表直线在矩阵中“画”出路线。
5. 代码
import numpy as np
def Lcs(X, Y, lenX, lenY):
c = [[0 for j in range(lenY)] for i in range(lenX)]
flag = [[0 for j in range(lenY)] for i in range(lenX)]
for i in range(lenX): #矩阵第一行和第一列赋值
if X[i] != Y[0]:
c[i][0] = c[i-1][0]
flag[i][0]='|'
else:
c[i][0] = 1
flag[i][0] = '√'
for j in range(lenY):
if X[0] != Y[j]:
c[0][j] = c[0][j-1]
flag[0][j]='-'
else:
c[0][j] = 1
flag[0][j] = '√'
for i in range(1,lenX): #按照公式挨个填入矩阵
for j in range(1,lenY):
if X[i] == Y[j]:
c[i][j] = c[i-1][j-1] + 1
flag[i][j] = '√'
elif c[i][j-1] > c[i-1][j]:
c[i][j] = c[i][j-1]
flag[i][j] = '-'
else:
c[i][j] = c[i-1][j]
flag[i][j] = '|'
return c, flag
def printLcs(flag, X, i, j):
if flag[i][j] == '√': #flag=0的确定要输出,并且往左上找
if i!=0 and j!=0:
printLcs(flag, X, i - 1, j - 1)
print(X[i], end='')
return
elif flag[i][j] == '-': #flag=1的往左找
printLcs(flag, X, i, j - 1)
else: #flag=2的往上找
printLcs(flag, X, i - 1, j)
X = input("请输入字符串X:")
Y = input("请输入字符串Y:")
c, flag = Lcs(X, Y, len(X), len(Y))
c, flag =np.array(c),np.array(flag)
print('lcs长度矩阵如下:\n',c)
print('lcs来源矩阵如下,从右下角开始按照直线寻找,打勾的代表该位置字符属于lcs\n',flag)
print("X和Y的最长公共子序列为:", end='')
printLcs(flag, X, len(X)-1, len(Y)-1)
测试数据1:
测试数据2: