对动态规划算法的一种理解——以Levenshtein距离算法为例

目录

摘要

问题简介

典型算法

基于函数微分的理解

状态转移方程

二元函数泰勒公式

对比

讨论:

总结


摘要

本文尝试从微积分角度对动态规划算法,尤其是其状态转移函数,提出一种理解。本文首先介绍Levenshtein 距离(编辑距离)算法,以其为例对比二元函数一节泰勒公式与动态规划状态转移函数的相似点,为有高等数学基础的读者提供一种对动态规划新的理解方式。这种理解可拓展到所有双字符串动态规划算法。

关键字:Levenshtein 距离,编辑距离,动态规划,状态转移函数,二元函数,泰勒公式,微分

问题简介

Levenshtein 距离,又称编辑距离,指的是两个字符串之间,由一个转换成另一个所需的最少编辑操作次数。许可的编辑操作包括将一个字符替换成另一个字符,插入一个字符,删除一个字符。编辑距离的算法是首先由俄国科学家 Levenshtein 提出的,故又叫 Levenshtein Distance 。

例如:

字符串A: abcdefg

字符串B: abcdef

通过增加或是删掉字符 ”g” 的方式达到目的。这两种方案都需要一次操作。把这个操作所需要的次数定义为两个字符串的距离。

给定两个字符串,求它们的Levenshtein距离[1]。

典型算法

对照参考下图,建立基本思路:

一个参考表格,规模 (3+1) x (5+1) [2]
  1. tab[i][j]用来表示字符串a的[0:i+1]和字符串b[0:j+1]的Levenshtein距离;
  2. 如果a[i] == b[j],则说明a[i]和b[j]同时分别加入a[0:i],b[0:j]之后不会改变两者的Levenshtein距离,问题规模减一,tab[i][j] = tab[i-1][j-1] + 0;
  3. 如果a[i] != b[j],我们从减而治之的角度出发,需要考虑四种可能情况:
    1. 先把b[0:j+1]编辑成a[0:i],用了tab[i-1][j]步,然后在a[0:i]后插入字符a[i],即可得到a[0:i+1],即tab[i][j] = tab[i-1][j] + 1;
    2. 先把a[0:i+1]编辑成b[0:j],用了tab[i][j-1]步,然后在b[0:j]后插入字符b[j],即可得到b[0:j+1],即tab[i][j] = tab[i][j-1] + 1;这与情况1是对称的。
    3. 先将a[0:i]编辑成b[0:j],用了tab[i-1][j-1]步,然后考虑将字符a[i]修改为b[j],用了1步,因此两种情况结合起来就是tab[i][j] = tab[i-1][j-1] + 1;
    4. 先将b[0:j]编辑成a[0:i],用了tab[i-1][j-1]步,然后考虑将字符b[j]修改为a[i],用了1步,因此两种情况结合起来就是tab[i][j] = tab[i-1][j-1] + 1;这与情况3不仅是对称的,其公式是相同的
  4. 这四种情况分别从不同的角度将问题规模降低了,我们应该求四种情况的最小值作为真正的结果。

注意情况1和2其实也可以用删除操作来描述,但是为了方便,这里统一按插入操作来描述。插入和删除操作互为逆过程:a删除指定字符变b等同于b插入指定字符变a,它们是对称的。

一种Python实现如下。

# python3

str1 = input().strip()
str2 = input().strip()

m = len(str1)
n = len(str2)

# 初始化二维表,为第一列和第一行添加基值
tab = []
for i in range(m+1):
    tab.append([])
    for j in range(n+1):
        if i == 0:
            tab[i].append(j)
        elif j == 0:
            tab[i].append(i)
            break

# 逐行更新表
for i in range(m):
    for j in range(n):
        if str1[i]==str2[j]:
            tab[i+1].append( tab[i][j] )
        else:
            tab[i+1].append( min(tab[i+1][j], tab[i][j], tab[i][j+1]) + 1 )

print(tab[i+1][j+1])

基于函数微分的理解

​状态转移方程属于一种递推公式,递推公式描述了一系列状态中相关联的局部几个状态之间的关系,因此,可以将递推公式理解为离散数学中的微分方程。接下来对比编辑距离问题中状态转移方程与二维函数泰勒公式,以通过微积分中的概念帮助理解状态转移方程。

状态转移方程

状态转移方程表示为

if a[i] == b[j]:
    t[i+1][j+1] = t[i][j]
else:
    t[i+1][j+1] = min( t[i][j], t[i+1][j], t[i][j+1] ) + 1

这里对t[i][j]的操作有一些别扭,是因为“修改”实际上应该是“删除”+“增加”,但题意只认为“修改”为原子操作。若“修改”=“删除”+“增加”,则有

if a[i] == b[j]:
   t[i+1][j+1] = t[i][j]
else:
	t[i+1][j+1] = min(t[i+1][j], t[i][j+1] ) + 1

这状态转移方程同样是有意义的,其对应的字符串距离称为“类编辑距离”[3-4],可用于计算莱文斯坦比。我们以他为例进行对比。

二元函数泰勒公式

首先看二元函数在某点产生全增量后的函数值如何通过改点函数值及其导数获得

f\left( {x + \delta x,y + \delta y} \right) = f\left( {x + \delta x,y} \right) + \frac{​{\partial f\left( {x,y} \right)}}{​{\partial y}}\delta y + O\left( r \right)                                         (1)

f\left( {x + \delta x,y + \delta y} \right) = f\left( {x,y + \delta y} \right) + \frac{​{\partial f\left( {x,y} \right)}}{​{\partial x}}\delta x + O\left( r \right)                                         (2)

f\left( {x + \delta x,y + \delta y} \right) = f\left( {x,y} \right) + \left( {\frac{\partial }{​{\partial x}}\delta x + \frac{\partial }{​{\partial y}}\delta y{\rm{ }}} \right)f\left( {x,y} \right) + O\left( r \right)                           (3)

f\left( {\theta ,r + \delta r} \right) = f\left( {\theta ,r} \right) + \frac{​{\partial f\left( {\theta ,r} \right)}}{​{\partial r}}\delta r + O\left( r \right)                                                             (4)

公式1和2分别是x和y方向的一阶泰勒公式。公式3描述了通常形式的二元一阶泰勒公式[6],公式4为极坐标形式二元函数在径向增量的一阶泰勒公式公式。

对比

对“类编辑距离”的状态转移方程和上述公式进行对比。

        1. 在a[i] != b[j]情况下,根据问题条件,我们应该在上一个相邻状态的前提下进行一次“插入”操作,这里产生的增量为1。这正如公式1和公式2所描述的,首先获取既有的、在一个方向上产生增量后的函数值,然后再向另一个方向产生增量。

t[i+1][j+1] =

t[i+1][j]

+1

# 忽略

f\left( {x + \delta x,y + \delta y} \right) =f\left( {x + \delta x,y} \right)\frac{​{\partial f\left( {x,y} \right)}}{​{\partial y}}\delta yO\left( r \right)

t[i+1][j+1] =

t[i][j+1]

+1

# 忽略

f\left( {x + \delta x,y + \delta y} \right) =f\left( {x,y + \delta y} \right)\frac{​{\partial f\left( {x,y} \right)}}{​{\partial x}}\delta xO\left( r \right)

        2. 在a[i] == b[j]情况下,根据问题条件,状态可从(x,y)直接跳到(x+dx,y+dx),同时由于字符串相同,根据规则,增量为0。这正如公式4所描述的。此时原函数值为t[i-1][j-1]。 

t[i+1][j+1] =

t[i][j]

+0

# 忽略

f\left( {\theta ,r + \delta r} \right) =f\left( {\theta ,r} \right)\frac{​{\partial f\left( {\theta ,r} \right)}}{​{\partial r}}\delta rO\left( r \right)

上述类比1中,对连续可微函数来说,两种计算方法应该得到相同的结果。但对离散函数来说,两种方法计算得到的结果可能不同。根据题意我们应当选择最小的结果。从概念上来说,其中min函数类似为对非解析函数选择了最短积分路径。

讨论:

以上是对“类编辑距离”的类比解释,“修改”被认为是+2增量的。而在编辑距离中,“修改”被认为是+1增量的,读者不妨想一下对编辑距离的类比是怎样的。

最长公共子串(longest common substring)也是常用的一种用于评估两段文本间相似度的方法。其求解结构与编辑距离求解非常类似[5]。读者可以尝试自己对比其状态转移方程与二元函数泰勒公式的相似之处。

总结

在面对问题时,若比较难建立思路,或感觉需要递推/递归算法,或常规方法时间复杂度过高时,可以考虑动态规划算法。动态规划算法的关键在于合理选取状态和设计状态转移方程。本文通过类比离散的状态转移方程和连续的二维函数泰勒公式,为理解状态转移方提供了一种思路,帮助开发人员更好的理解动态规划算法。

参考资料

[1]计算字符串的编辑距离_牛客题霸_牛客网: https://www.nowcoder.com/practice/3959837097c7413a961a135d7104c314

[2] 题解 | #计算字符串的距离#_牛客博客: https://blog.nowcoder.net/n/38870d424dfa46ed868660fc0ae2423e

[3] Python如何计算编辑距离? - 简书 https://www.jianshu.com/p/9a53f32cf62b

[4] Levenshtein莱文斯坦算法在项目中的应用 - Tsissan - 博客园: https://www.cnblogs.com/lookingforfreedom/p/15651051.html

[5] NLP笔记:浅谈字符串之间的距离 - 云+社区 - 腾讯云: https://cloud.tencent.com/developer/article/1806517

[6] 二元函数泰勒公式 - 豆丁网: https://www.docin.com/p-2183421712.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值