动态规划与字符串的编辑距离

动态规划

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

在很多情况下动态规划比穷举高效,但应用动态规划法解题的效率,取决于问题的类型。对于多项式时间的问题,动态规划法可能得到多项式时间复杂度的高效算法,但是对于NP问题,动态规划法也只能得到指数时间复杂度的算法。

动态规划法可以用于含有线性或非线性递推关系的最优解问题,但这些问题都必须满足最优化原理和子问题的“无后向性”。

最优化原理是问题的最优子结构的性质,如果一个问题的最优子结构是不论过去状态和决策如何,对前面的决策所形成的状态而言,其后的决策必须构成最优策略。也就是说,不管之前决策是否是最优决策,都必须保证从现在开始的决策是在之前决策基础上的最优决策,则这样的最优子结构就符合最优化原理。

“无后向性”是指当各个阶段的子问题确定以后,对于某个特定阶段的子问题来说,它之前的各个阶段的子问题的决策只影响该阶段的决策,对该阶段之后的决策不产生影响,也就是说,每个阶段的决策仅受之前决策的影响,但是不影响之后各阶段的决策。

基本思想

和分治法一样,动态规划解决复杂问题的思路是对问题进行分解,通过求解小规模的子问题再反推出原问题的结果。但动态规划是沿着决策阶段分解子问题的,决策的阶段可以随时间划分,也可以对着问题的演化状态划分。动态规划法的子问题可以不是互相独立的,通常有包含关系,甚至两个子问题可以有两个相同的子子问题。

动态规划法没有具体的实现模式,可以用带备忘录的递归方法实现,也可以根据堆叠子问题之间的递推公式用递推的方法实现。从算法设计的角度分析,一般需要四个步骤:定义最优子问题、定义状态、定义决策和状态转换方程以及确定边界条件。

四个步骤

1.定义最优子问题

定义最优子问题就是确定问题的优化目标以及如何决策最优解,并对决策过程划分阶段。阶段可以理解为一个问题从开始到解决需要经过的环节,这些环节前后关联。

2.定义状态

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

3.定义决策和状态转换方程

决策就是能使状态发生转变的选择动作,如果选择动作有多个,则决策就是取其中能使得阶段结果最优的那一个。状态转换方程是描述状态转换关系的一系列等式,也就是从n-1阶段到n阶段演化的规律。状态转换取决于子问题的堆叠方式,如果状态定义得不合适,就会导致子问题之间没有重叠,就不存在状态转换关系了。算法就退化为朴素递归搜索算法。

4.确定边界条件

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

三个例子

1.装配站问题

装配站问题的阶段划分比较清晰,把工件从一个装配站移到下一个装配站就可以看作是一个阶段,其子问题就可以定义为从一个装配站转移到下一个装配站,直到最后一个装配站完成工件组装。

其实质就是在不同的装配线之间选择装配站,使得工件装配完成的时间最短,其状态s[i,j] s[i,j]s[i,j]就可以定义为通过第i ii条装配线的第j jj个装配站所需要的最短时间。

其决策就是选择在当前工作线上的下一个工作站继续装配,或者花费一定的开销将其转移到另一条工作线上的下一个工作站继续装配。如果定义a[i,j] a[i,j]a[i,j]为第i ii条工作线的第j jj个装配站需要的装配时间,k[i,j] k[i,j]k[i,j]为从另一条工作线转移到第i ii条工作线的第j jj个装配站需要的转移开销,则装配站问题的状态转换方程可以描述为:

s[1,j=min(s[1,j−1]+a[1,j],s[2,j−1+k[1,j]+a[1,j]])] s[1,j=min(s[1,j-1]+a[1,j],s[2,j-1+k[1,j]+a[1,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]])] s[2,j=min(s[2,j-1]+a[2,j],s[1,j-1+k[2,j]+a[2,j]])]

s[2,j=min(s[2,j−1]+a[2,j],s[1,j−1+k[2,j]+a[2,j]])]

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

s[1,1]=k[1,1]+a[1,1] s[1,1]=k[1,1]+a[1,1]

s[1,1]=k[1,1]+a[1,1]

s[2,1]+k[2,2]+a[2,2] s[2,1]+k[2,2]+a[2,2]

s[2,1]+k[2,2]+a[2,2]

2.背包问题

每选择装一个物品就可以看作一个阶段,其子问题就可以定义为每次向背包中装一个物品,直到超过背包的最大容量为止。

背包问题本身是一个线性过程,但是简单将状态定义为装入的物品编号,也就是定义s[i] s[i]s[i]为装入第i ii件物品后获得的最大价值,则子问题无法满足“无后向性”要求,原因是之前的任何一个决策都会影响到所有的后序决策(因为装入物品后背包容量发生了变化),因此需要增加一个维度的约束。考虑到每装入一个物品,背包的剩余容量就会减少,故而选择将背包容量也包含在状态定义中。最终背包问题的状态s[i,j] s[i,j]s[i,j]定义为将第i ii件物品装入容量为j jj的背包中所能获得的最大价值。

背包问题的决策很简单,就是判断装入第i ii件物品获得的收益最大还是不装入第i ii件物品获得的收益最大。如果不装入第i ii件物品,则背包内物品的价值仍然是s[i−1,j] s[i-1,j]s[i−1,j]状态,如果装入第i ii件物品,则背包内物品的价值就变成s[i,j−Vi]+Pi s[i,j-V_i]+P_is[i,j−V i]+Pi

状态,其中Vi V_iV i和Pi P_iPi

分别是第i ii件物品的容积和价值,决策的状态转换方程就是:

s[i,j]=max(s[i−1,j],s[i,j−Vi]+Pi) s[i,j]=max(s[i-1,j],s[i,j-V_i]+P_i)

s[i,j]=max(s[i−1,j],s[i,j−V i]+Pi)

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

s[0,Vmax]=0 s[0,V_{max}]=0s[0,V max]=0

3.最长公共子序列问题

可以按照问题的演化状态划分阶段,这需要首先定义状态,有了状态的定义,只要状态发生了变化,就可以认为是一个阶段。

如果定义str1[1...i] str1[1...i]str1[1...i]为第一个字符串前i ii个字符组成的子串,定义str2[1...j] str2[1...j]str2[1...j]为第二个字符串的前j jj个字符组成的子串,则最长公共子序列问题的状态s[i,j] s[i,j]s[i,j]定义为str1[1...i] str1[1...i]str1[1...i]与str2[1...j] str2[1...j]str2[1...j]的最长公共子序列长度。

决策方式就是判断str1[i] str1[i]str1[i]和str2[j] str2[j]str2[j]的关系,如果str1[i] str1[i]str1[i]和str2[j] str2[j]str2[j]相同,则公共子序列的长度应该是s[i−1,j−1]+1 s[i-1,j-1]+1s[i−1,j−1]+1,否则就分别尝试匹配str1[1...i−1] str1[1...i-1]str1[1...i−1]与str2[1...j] str2[1...j]str2[1...j]的最长公共子串,以及str1[1...i] str1[1...i]str1[1...i]与str2[1...j−1] str2[1...j-1]str2[1...j−1]的最长公共子串,然后取二者中较大的那个值作为s[i,j] s[i,j]s[i,j]的值。状态转换方程就是:

s[i,j]=s[i−1,j−1]+1;str1[i]与str2[j]相同 s[i,j]=s[i-1,j-1]+1;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]);str1[i]与str2[j]不相同 s[i,j]=max(s[i,j-1],s[i-1,j]);str1[i]与str2[j]不相同

s[i,j]=max(s[i,j−1],s[i−1,j]);str1[i]与str2[j]不相同

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

s[i,j]=0;i=0或j=0 s[i,j]=0;i=0或j=0

s[i,j]=0;i=0或j=0

字符串的编辑距离

把两个字符串的相似度定义为:将一个字符串转换成另外一个字符串时需要付出的代价。转换可以采用插入、删除和替换三种编辑方式,因此转换的代价就是对字符串的编辑次数。字符串转换的方式不唯一,不同的转换方法需要的编辑次数也不一样,最少的那个编辑次数就是字符串的编辑距离。

采用朴素的递归算法时间复杂度是O(3n) O(3^n)O(3n),对于两个长度为5的字符串“SNOWY”和“SUNNY”,递归调用的次数是241次,接近于35 3^53

5

这个量级,当字符串的长度非常大的时候,这个算法将不能接受。因此要采用动态规划法对这个算法进行改进。

解决策略

这个问题的阶段划分不是很明显。首先定义问题的状态,在从状态转换关系入手定义阶段和子问题的递推关系。假设source字符串有n个字符,target字符串有m个字符,如果将问题定义为求解将source的[1⋯n] [1cdots n][1⋯n]个字符转换为target的[1⋯m] [1cdots m][1⋯m]个字符所需要的最少编辑次数(编辑距离),则其子问题就可以定义为将source的前[1⋯i] [1cdots i][1⋯i]个字符转换为target的[1⋯j] [1cdots j][1⋯j]个字符所需要的最少编辑次数,这就是这个问题的最优子结构。因此,将状态d[i,j] d[i,j]d[i,j]定义为从子串source[1⋯i] [1cdots i][1⋯i]到子串target[1⋯j] [1cdots j][1⋯j]之间的编辑距离。

根据决策方式,d[i,j] d[i,j]d[i,j]的递推关系分为两种情况,分别是source[i] [i][i]等于target[j] [j][j]和source[i] [i][i]不等于target[j] [j][j],两种情况下d[i,j] d[i,j]d[i,j]的递推关系如下:

d[i,j]=d[i−1,j−1]+0;source[i]等于target[j] d[i,j]=d[i-1,j-1]+0;source[i]等于target[j]

d[i,j]=d[i−1,j−1]+0;source[i]等于target[j]

d[i,j]=min(d[i,j−1]+1,d[i−1,j]+1,d[i−1,j−1]+1);source[i]不等于target[j] d[i,j]=min(d[i,j-1]+1,d[i-1,j]+1,d[i-1,j-1]+1);source[i]不等于target[j]

d[i,j]=min(d[i,j−1]+1,d[i−1,j]+1,d[i−1,j−1]+1);source[i]不等于target[j]

当target字符串是空字符串时,编辑距离相当于将source字符串中的字符逐个删除的次数,可以确定一个边界条件为:

d[i,0]=source字符串的长度 d[i,0]=source字符串的长度

d[i,0]=source字符串的长度

同样如果source字符串的长度为0,编辑距离相当于在source字符串中逐个插入target字符的次数,另一个边界条件为:

d[0,j]=target字符串的长度 d[0,j]=target字符串的长度

d[0,j]=target字符串的长度

def Levenshtein_Distance(s1, s2):    len_s1 = len(s1)    len_s2 = len(s2)    dp = [[0 for _ in range(len_s2+1)] for _ in range(len_s1 + 1)]    for i in range(len_s1 + 1):        for j in range(len_s2 + 1):            if i == 0:                dp[i][j] = j            elif j == 0:                dp[i][j] = i            elif s1[i - 1] == s2[j - 1]:                dp[i][j] = dp[i - 1][j - 1]            else:                dp[i][j] = 1 + min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1])    return dp[len_s1][len_s2]def main():    option = input('是否选择使用默认字符串(Y/N): ')    if option == 'Y':        s1 = "I love you"        s2 = "I have been loving you for my whole life"    else:        s1 = str(input('请输入第一个字符串:'))        s2 = str(input('请输入第二个字符串:'))    distance = Levenshtein_Distance(s1, s2)    print("The distance from ""+s1+"" to ""+s2+"" is "+str(distance)+".")main()

运行

在fish终端中的运行结果如下

python距离向量路由算法_python算法练习——动态规划与字符串的编辑距离相关推荐

  1. python距离向量路由算法_路由算法(全网最细)

    我在复习过程中整理的面试系列文章,全部免费分享给大家,适合保研和考研,需要的请移步我的个人原创公zhong号:程序员宝藏(号如其名,诚不欺你),回复关键字:复试上岸,即可获取! 正文开始 正文开始 1 ...

  2. python中难的算法_Python算法很难吗?python神书《算法图解》PDF电子版分享给你

    许多小伙伴后台私信说,python算法让自己很头疼,有没有可以让算法像小说一样有趣的书籍资料呢?看这里吧!小宋为大家找到了这本<算法图解>的PDF电子版!让你在学习python的路上变得轻 ...

  3. python分治算法_Python算法:分治法

    本节主要介绍分治法策略,提到了树形问题的平衡性以及基于分治策略的排序算法 本节的标题写全了就是:divide the problem instance, solve subproblems recur ...

  4. 剑指offer有用python版的吗_Python算法面试通关,剑指offer就靠它了

    原标题:Python算法面试通关,剑指offer就靠它了 北上广容不下肉身, 三四线放不下灵魂, 程序员里没有穷人, 有一种土豪叫算法工程师. 算法,晦涩难懂,却又是IT领域最受重视的素养之一可以说, ...

  5. python编程的50种基础算法_Python算法新手入门大全

    干货:GitHub标星2.6万!Python算法新手入门大全 Python已经成为最受欢迎的程序设计语言之一.自从2004年以后,python的使用率呈线性增长.2011年1月,它被TIOBE编程语言 ...

  6. python rfind函数用法_Python语法速查:字符串格式简单处理、子串查找与判断方法?...

    这是一篇python基础知识分享型文章,对学习python感兴趣的朋友们可以仔细看看 字符串常用方法 Python3中,字符串全都用Unicode形式,所以省去了很多以前各种转换与声明的麻烦.字符串属 ...

  7. python练习题百度云_Python专项基础练习(字符串)练习题

    1. 字符串练习题 1.1.字符串内置方法练习 在交互式解释器中完成下列题目将字符串 "abcd" 转成大写 计算字符串 "cd" 在 字符串 "ab ...

  8. python距离向量路由算法_互联网中常用路由协议,路由协议基础,一分钟了解下...

    一.路由基础 Routing protocol 用于路由器动态寻找最优路径,并使路由器都拥有路由表,R/p 决定了数据包的上行路径,eg:RIP IGRP EIGRP OSPF,被动路由协议被分配到接 ...

  9. python 递归 写平方_Python算法:推导、递归和规约

    注:本节中我给定下面三个重要词汇的中文翻译分别是:Induction(推导).Recursion(递归)和Reduction(规约) 本节主要介绍算法设计的三个核心知识:Induction(推导).R ...

最新文章

  1. 函数指针--全局函数指针与类的函数指针(二)
  2. golang 并发与并行学习笔记(一)
  3. 3.1.5 改善模型的表现
  4. 【转载】谁记录了mysql error log中的超长信息
  5. more effective C++
  6. 事件驱动之JDK观察者模式
  7. 优化JS代码的34种方法(上)
  8. Net和T-sql中的日期函数操作
  9. android intent-filter category,android intent-filter category.DEFAULT
  10. Windows 8 系列(六):BackgroundTask 及其引起无法捕获的Crash
  11. iOS视频边下载边播放
  12. Linux快速构建apache web服务器
  13. 2008评估过期 server sql_SQLServer2008R2数据库评估版已经过期解决办法.doc
  14. hls ask 调制
  15. 饭后Android 第三餐-XUI框架(XUI介绍,使用方法,控件使用(九个Button,导航栏,可伸缩布局,顶部弹出框))
  16. vnc 键盘慢_在基于Web的VNC应用程序中支持多种键盘布局
  17. 普华集团翟山鹰:金融激荡30年 深度洞察金融的“奥秘”
  18. flash 与3D笔记:图片墙(1)
  19. VB.net应用技巧5: VB.net 除法运算
  20. 76、多边形一些基本操作(自相交、尖刺、保证逆时针、求交)

热门文章

  1. 浅析python中的main函数_浅的意思
  2. c语言 一元二次函数,计算一元二次函数的根,大家看看那里有错了。。。。
  3. java test20006_java 数组 (数组个数小于2000)
  4. OpenShift 4 - DevSecOps Workshop (7) - 为Pipeline增加向Nexus制品库推送任务
  5. OpenShift 4 Tekton - Tekton实现包含Gogs+SonaQube+Nexus+Report+WebHook的Pipeline
  6. 在Reporting Services (RDL)中自动生成大量列
  7. 使用face-api和Tensorflow.js在浏览器中进行AI年龄估计
  8. Visual Studio 2017中的第一个Python项目
  9. 新的恶意软件将后门植入微软 SQL Server 中
  10. SQL 使用总结四(关于索引)