我在讨论两个等长字符串的最长公共子序列的背景下讨论动态编程的笔记。有问题的算法输出长度(不是子串)。
所以我有两个字符串,比如说:
S = ABAZDC,T = BACBAD
最长的常见子序列是ABAD(子串不必是相邻的字母)
算法如下,其中LCS [i,j]表示S [1..i]和T [1..j]的最长公共子序列:
if S[i] != T[j] then
LCS[i, j] = max(LCS[i - 1, j], LCS[i, j - 1])
else
LCS[i, j] = 1 + LCS[i - 1, j - 1]
我的笔记声称您可以填写一个表格,其中每个字符串都沿轴写入。类似的东西:
B A C B A D
A 0 1 1 1 1 1
B 1 1 1 2 2 2
A ...
ž
d
C
两个问题:
1)我们如何实际开始填写此表。算法是递归的,但似乎没有提供基本情况(否则我只是调用LCS [5,5])?注释声称你可以用i和j做两个简单的循环,并在恒定时间内填写每个点......
2)我们如何修改算法,以便最长的公共子序列是相邻的字母?我的想法是,当我发现S中的下一个字母与T中的下一个字母不匹配时,我必须将当前子序列的长度重置为0.但是它很棘手因为我想要跟踪到目前为止看到的最长的时间(可能我看到的第一个子序列是最长的一个)。所以也许我有一个额外的参数,longestThusFar,当我们最初调用我们的算法并且在后续调用中改变时,它是0。
有人能让这一点更加严谨吗?
谢谢!
答案 0 :(得分:1)
首先,算法是递归的,但实现总是迭代的。换句话说,我们没有显式地从函数本身调用相同的函数(递归)。
我们使用已填充的表条目来补偿递归。
说,你有两个长度为 M 的字符串。
然后表定义了维度(M + 1)X(M + 1)。
for(i = 0 to M)
{
LCS[0][i]=0;
}
for(i = 1 to M)
{
LCS[i][0]=0;
}
你得到一张像
这样的表格 B,A,C,B,A,D
0,0,0,0,0,0,0
A 0
B 0
A 0
Z 0
D 0
C 0
0th col 中的每个零意味着如果不考虑字符串 BACBAD 的字符,则LCS的长度= 0.
第0行中的每个零意味着如果不考虑字符串 ABAZDC 的字符,则LCS的长度= 0.
其余条目使用您提到的规则填写。
for(i = 1 to M)
{
for(j = 1 to M)
{
if S[i-1] != T[j-1] then
LCS[i, j] = max(LCS[i - 1, j], LCS[i, j - 1])
else
LCS[i, j] = 1 + LCS[i - 1, j - 1]
}
}
请注意, S [i-1]!= T [j-1] 而不是 S [i]!= T [j] ,因为当你填写 LCS [i,j] ,您总是在比较第i个第1个字符的S和第j个第1个字符。
LCS的长度由 LCS [M,M] 给出。
理解这一点的最好方法是亲手试试。
在回答第二个问题时,您不需要修改算法。
解决方案位于用于检索LCS的表中。
为了检索LCS,我们制作了一个额外的 T 字样 MXM 。我们修改算法如下。
for(i = 1 to M)
{
for(j = 1 to M)
{
if S[i-1] != T[j-1] then
{
LCS[i, j] = max(LCS[i - 1, j], LCS[i, j - 1])
if(LCS[i - 1, j]>=LCS[i, j - 1])
T[i-1][j-1]='u'//meaning up
else T[i-1][j-1]='l'//meaning left
}
else
{
LCS[i, j] = 1 + LCS[i - 1, j - 1]
T[i-1][j-1]='d'//meaning diagonally up
}
}
}
现在,为了知道两者共有的最长子串(OF ADJACENT LETTERS),沿对角线遍历。
长度=在对角线中找到的连续 d's 的最大数量。
任何方阵 NXN 的对角遍历均由。
完成。下三角包括主对角线
j=N-1
while(j>=0)
{
i=j;k=0;
while(i <= N-1)
{
entry T[i][k];
++i;++k
}
--j;
}
上三角
j=1;
while(j<=N-1)
{
i=j;k=0;
while(i<=N-1)
{
entry T[k][i];
++k;++i;
}
--j;
}