动态规划
和分治法一样,动态规划(dynamic programming)是通过组合子问题而解决整个问题的解。
分治法是将问题划分成一些独立的子问题,递归地求解各子问题,然后合并子问题的解。
动态规划适用于子问题不是独立的情况,也就是各子问题包含公共的子子问题。
此时,分治法会做许多不必要的工作,即重复地求解公共的子问题。动态规划算法对每个子问题只求解一次,将其结果保存起来,从而避免每次遇到各个子问题时重新计算答案。
适用范围
1.问题中的状态满足最优性原理。
2.问题中的状态必须满足无后效性。
最优性原理体现为问题的最优子结构特性。当一个问题的最优解中包含了子问题的最优解时,则称该问题具有最优子结构特性。最优性原理是动态规划的基础。任何一个问题,如果失去了这个最优性原理的支持,就不可能用动态规划设计求解。
所谓无后效性是指:“下一时刻的状态只与当前状态有关,而和当前状态之前的状态无关,当前状态是对以往决策的总结”。
动态规划算法的设计
两种方法:
自顶向下(又称记忆化搜索、备忘录):基本上对应着递归函数实现,从大范围开始计算,要注意不断保存中间结果,避免重复计算
自底向上(递推):从小范围递推计算到大范围
动态规划的重点
递归方程+边界条件
最长公共子串(The Longest Common Substring)
LCS问题就是求两个字符串最长公共子串的问题。
解法就是用一个矩阵来记录两个字符串中所有位置的两个字符之间的匹配情况,若是匹配则为1,否则为0。然后求出对角线最长的1的序列,其对应的位置就是最长匹配子串的位置。
def find_lcsubstr(s1, s2):
m=[[0 for i in range(len(s2)+1)] for j in range(len(s1)+1)] #生成0矩阵,为方便后续计算,比字符串长度多了一列
mmax=0 #最长匹配的长度
p=0 #最长匹配对应在s1中的最后一位
for i in range(len(s1)):
for j in range(len(s2)):
if s1[i]==s2[j]:
m[i+1][j+1]=m[i][j]+1
if m[i+1][j+1]>mmax:
mmax=m[i+1][j+1]
p=i+1
return s1[p-mmax:p],mmax #返回最长子串及其长度
最长公共子序列 (The Longest Common Subsequence)
子串要求字符必须是连续的,但是子序列就不是这样。
最长公共子序列是一个十分实用的问题,它可以描述两段文字之间的“相似度”,即它们的雷同程度,从而能够用来辨别抄袭。对一段文字进行修改之后,计算改动前后文字的最长公共子序列,将除此子序列外的部分提取出来,这种方法判断修改的部分,往往十分准确。
解法就是用动态回归的思想,一个矩阵记录两个字符串中匹配情况,若是匹配则为左上方的值加1,否则为左方和上方的最大值。一个矩阵记录转移方向,然后根据转移方向,回溯找到最长子序列。
import numpy
def find_lcseque(s1, s2):
# 生成字符串长度加1的0矩阵,m用来保存对应位置匹配的结果
m = [ [ 0 for x in range(len(s2)+1) ] for y in range(len(s1)+1) ]
# d用来记录转移方向
d = [ [ None for x in range(len(s2)+1) ] for y in range(len(s1)+1) ]
for p1 in range(len(s1)):
for p2 in range(len(s2)):
if s1[p1] == s2[p2]: #字符匹配成功,则该位置的值为左上方的值加1
m[p1+1][p2+1] = m[p1][p2]+1
d[p1+1][p2+1] = 'ok'
elif m[p1+1][p2] > m[p1][p2+1]: #左值大于上值,则该位置的值为左值,并标记回溯时的方向
m[p1+1][p2+1] = m[p1+1][p2]
d[p1+1][p2+1] = 'left'
else: #上值大于左值,则该位置的值为上值,并标记方向up
m[p1+1][p2+1] = m[p1][p2+1]
d[p1+1][p2+1] = 'up'
(p1, p2) = (len(s1), len(s2))
print numpy.array(d)
s = []
while m[p1][p2]: #不为None时
c = d[p1][p2]
if c == 'ok': #匹配成功,插入该字符,并向左上角找下一个
s.append(s1[p1-1])
p1-=1
p2-=1
if c =='left': #根据标记,向左找下一个
p2 -= 1
if c == 'up': #根据标记,向上找下一个
p1 -= 1
s.reverse()
return ''.join(s)