最长公共子序列与编辑距离动态规划原理分析

来源:互联网 发布:淘宝猫粮店推荐 编辑:程序博客网 时间:2024/06/15 10:17

前段时间看过最长公共子序列的动态规划算法,这两天又看到了编辑距离的动态规划算法,感觉两者有很相似的地方,而状态转移方程又不十分直观,所以打算把其原理记录下来,以防以后忘记。

先看最长公共子序列,记两个序列分别为a[m],b[n].其状态转移方程如下:

从中我们可以看出分成两种情况,第一种是a[i]=b[j],第二种是a[i]!=b[j]

1.a[i]=b[j]。这种情况还是比较显然的,既然a[i]=b[j]了,我们就可以将a[0-i],b[0-j]如下排列:

a[0] a[1] ..... a[i-1] a[i]

       b[0]  ....b[j-1]b[j]

这时候我们就把a[i],b[j]从上面这两个个序列中去掉,然后就变成了a[0-(i-1)],b[0-(j-1)]这种情况了,而这种情况我们已经知道c[i-1][j-1]了,所以c[i][j]就比c[i-1][j-1]多了一项a[i]与b[j]匹配的情况,所以c[i][j]=c[i-1][j-1]+1。

当然有人可能就奇怪了,为啥能一定保证a[i]b[j]匹配时的情况一定是公共子序列最大的情况,如果a[i]与b[j]不匹配说不定c[i][j]更大呢。那我们就看一下这种情况:

假设在某种匹配方式下,a[i]与b[j]不匹配。这时a[i]与b[0-(j-1)]的某个数匹配,或b[j]与a[0-(i-1)]中的某个数匹配(如果这两种情况都不发生,说明a[i]、b[j]都不在最长公共子序列k中,这时如果把a[i]与b[j]匹配的这一组加入k中得到K,显然K>k,这说明k不是最长公共子序列,产生了矛盾)。不妨设发生了a[i]与b[0-(j-1)]的某个数匹配的情况,如下

a[0] a[1] ..... a[i-1] a[i]  

       b[0]  ....b[j-k-1]b[j-k]  b[j-k+1] .....b[j]

这时候,a[i-1]就只能与b[j-k]之前的数匹配了。那么,在这种匹配方式下的c`[i][j]=c[i-1][j-k-1]+1。而根据状态转移方程,c[i-1][j-1]>=c[i-1][j-k-1](使用数学归纳法),所以,a[i]b[j]匹配的c[i][j]>=c`[i][j].这说明,我们假设中的a[i]与b[j]不匹配的匹配方式不会是唯一最优的方式,而a[i]b[j]匹配的匹配方式则是一种最优的匹配方式。

2.a[i]!=b[j]。

这时候a[i]b[j]就不可能再匹配上了。然而在求a[0-i]b[0-j]的最长公共子序列时,我们还可以人为地分为3种情况:

1、a[i]不在最长公共子序列中

2. b[j]不在最长公共子序列中

3.a[i]和b[j]都不在最长公共子序列中.

先看情况1、2。12这两种情况本质上是一种,所以不妨设是第二种情况,这时候最长公共子序列中就没有b[j]什么事了,显然a[0-i]b[0-j]的最长公共子序列与a[0-i]b[0-(j-1)]的最长公共子序列是完全一样的。所以 c[i][j]=c[i][j-1]

再看情况3.如果在a[0-i]b[0-j]最长公共子序列中b[j]不存在,说明b[j]对于其最长公共子序列没有任何影响,那么c[i][j]=c[i][j-1]。同理,c[i][j]=c[i-1][j]

这样这三种情况都讨论完了,但是在真正执行的时候我们可不知道到底是哪种情况最好。那就把三种情况都算出来,取三种情况中最大的,即c[i][j]=max(c[i-1][j],c[i][j-1])

那么情况1、2综合起来,加上i、j=0的情况,就得到了上图中的状态转移方程.



然后再来看编辑距离问题。这一问题因为涉及到能对其中一个字符串进行增添、删除、替换操作,情况变得更为复杂。但思路上都是分情况讨论,然后分别证明在每种情况下公式都是对的。

其状态转移方程如下

  • if i == 0 且 j == 0,edit(i, j) = 0
  • if i == 0 且 j > 0,edit(i, j) = j
  • if i > 0 且j == 0,edit(i, j) = i
  • if i ≥ 1  且 j ≥ 1 ,edit(i, j) == min{ edit(i-1, j) + 1, edit(i, j-1) + 1, edit(i-1, j-1) + f(i, j) },当第一个字符串的第i个字符不等于第二个字符串的第j个字符时,f(i, j) = 1;否则,f(i, j) = 0。
假设我们需要操作a[m]使其变换到b[n]
1、2、3情况都比较显然,就不再证明,下面来看第四种情况:

第四种情况说明对a[0-i]的最少操作次数都是在对a[0-(i-1)]操作的基础上进行的:edit(i-1, j) + 1相当于a[0-i]先删去a[i]变成a[i-1]后再与b[0-j]匹配,edit(i, j-1) + 1相当于a[0-i]添加了一个a[i+1]=b[j]后再将a[0-i]与b[0-(j-1)]匹配(a添加b[j]等效于b删去b[j]),edit(i-1, j-1) + f(i, j)相当于使a[i]替换为b[j]后,继续进行a[i-1]到p[j-1]的操作。

同样的问题,为什么这样做操作次数就会最少呢?

a[i]=b[j]的情况比较显然,分析过程同下面,略过。

a[i]!=b[j]时,假设存在某种操作方式m,使得edit(m)=edit(i,j)<min{ edit(i-1, j) + 1, edit(i, j-1) + 1, edit(i-1, j-1) + f(i, j) }

那么我们来看一下在操作m后 变换为b[j]的a[p]的来源。

        1、若a[p]来自于向a中插入了一个新数aa,那么我们采取操作m来变换a[0-i],但去掉添加aa的过程,那么最终的效果就是a[0-i]变为b[0-(j-1)]。这时候这种操作的操作次数edit`(i,j-1)=edit(m)-1<edit(i,j-1),这样就产生矛盾了(因为按照前提,不可能存在操作次数比edit(i,j-1)还少的从a[0-i]变化到b[0-(j-1)]的过程),说明若a`[j]来自于向a中插入了一个新数aa,不可能存在这种操作m

2、若a[p]来自于a[k](k<i,a[k]=b[j])(a[k]也可能是由替换产生的),即:将a[k]以后的数都删去。那么,显然对a[0-(i-1)]执行操作m,但去掉"删除a[i]"这一操作,最终的结果就是a[0-(i-1)]变为b[0-j]。这时候edit`(i-1,j)=edit(m)-1<edit(i,j-1),矛盾。

3。、若a[p]来自于a[i]替换为b[j],那么,显然对a[0-(i-1)]执行操作m,但去掉"替换a[i]"这一操作,最终的结果就是a[0-(i-1)]变为b[0-(j-1)]。这时候edit`(i-1,j)=edit(m)-1<edit(i-1,j-1),矛盾。

综上123所述,不可能存在比min{ edit(i-1, j) + 1, edit(i, j-1) + 1, edit(i-1, j-1) + f(i, j) }更少的操作次数,否则就会产生矛盾。同时,根据红色部分的分析,我们知道,通过min{ edit(i-1, j) + 1, edit(i, j-1) + 1, edit(i-1, j-1) + f(i, j) }这一公式计算出的最小编辑次数是有方法来实际进行执行的。

上面就是最长公共子序列和编辑距离的动态规划方程的原理分析,比较繁琐,感觉发明算法的人思路应该不是这样,但对理解方程应该也有一些帮助。同样问题的还有最长公共子串问题,个人感觉还是比较容易理解,就不再具体分析。

0 0
原创粉丝点击