在参阅《A Longest Common Subsequence Algorithm Suitable for Similar Text Strings》(Narao Nakatsu,Yahiko Kambayashi,Shuzo Yajima著)后。发现该算法可以利用线性空间求出最长公共子序列。该算法的时间占用O(n(m-p+1)),p为最长公共子序列的长度。
字符串A和字符串B,计算LCS(A,B)
定义一:设M=Len(A),N=Len(B),不妨设M≤N。
定义二:A=a1a2……aM,表示A是由a1a2……aM这M个字符组成
B=b1b2……bN,表示B是由b1b2……bN这N个字符组成
LCS(i,j)=LCS(a1a2……ai,b1b2……bj),其中1≤i≤M,1≤j≤N
定义三:L(k,i)表示,所有与字符串a1a2……ai有长度为k的LCS的字符串b1b2……bj中j的最小值。
用公式表示就是:L(k,i)=Min{j} Where LCS(i,j)=k
用一个例子来说明:A="CD",B="CEFDRT"。
很明显的是LCS(2,1)=1,LCS(2,2)=1,LCS(2,3)=1。
满足LCS(2,j)=1这个条件的j有三个,分别是j=1、j=2、j=3。其中j最小值是1。故L(1,2)=1
为了推导L的计算,有下面几个定理。
定理一:任意的i,1≤i≤M。有L(1,i)<L(2,i)<L(3,i)……
定理二:任意的i,1≤i≤M-1。任意的k,1≤k≤M。有L(k,i+1)≤L(k,i)
定理三:任意的i,1≤i≤M-1。任意的k,1≤k≤M-1。有L(k,i)<L(k+1,i+1)
定理四:如果L(k,i+1)存在,则L(k,i+1)的计算公式为
L(k,i+1)=Min{Min{j},L(k,i)} Where {ai+1=bj And j>L(k-1,i)}
上面四个定理证明从略。可以从上面四个定理推导出L的计算。
故,L的计算公式为
①L(1,1)=Min{j} Where {a1=bj}
②L(1,i)=Min{Min{j} Where {ai=bj},L(1,i-1)} 此时,1
③L(k,i)=Min{Min{j} Where {ai=bj And j>L(k-1,i-1)},L(k,i-1)} 此时,1
注:以上公式中,若找不到满足Where后面条件的j,则j=MaxValue
当i<k时,则L(k,i)=MaxValue
MaxValue是一个常量,表示“不存在”
在实际的算法实现中,为了简化运算,再次提出几个定义。
定义: L(0,i)=0 1≤i≤M
L(k,0)=MaxValue 1 MaxValue=N+1 (只要定义一个j不可能取到的值就可以了) 则,在①中的公式可以写成 L(1,1)=Min{j} Where {a1=bj}=Min{j} Where {a1=bj And j>0 } =Min{Min{j} Where {a1=bj And j>0 },MaxValue} =Min{Min{j} Where {a1=bj And j>L(0,0) },L(1,0)} 在②中的公式可以写成 L(1,i)=Min{Min{j} Where {ai=bj},L(1,i-1)} =Min{Min{j} Where {ai=bj And j>0},L(1,i-1)} =Min{Min{j} Where {ai=bj And j>L(0,i)},L(1,i-1)} 此时,1
于是,三个公式统一了 ④L(k,i)=Min{Min{j} Where {ai=bj And j>L(k-1,i-1)},L(k,i-1)} 此时,1≤i≤M,1≤k≤M 且当i<k时,则L(k,i)=MaxValue 仔细观察④,公式还可以写成如下 ⑤ L(k,i)=Min{j} Where {ai=bj And L(k-1,i-1) 或 L(k,i)=L(k,i-1) 1≤i≤M,1≤k≤M,当j不存在时 写成⑤的目的有两个:一个是简化计算,不计算不必要的值;一个是为了标记,为后面计算最长公共子序列做准备。 接下来,将会用例子来说明: A:481234781;B:4411327431 第一步:初始化L矩阵 第二步:如表格所示,计算第一条对角线 运气很差,只有第一个单元格有值,其余的都是V(MaxValue),很显然这不是问题的解。这条对角线满足L(k-1,i-1) 第三步:如表格所示,计算相邻的第二条对角线 运气比较好,有四个值。说明LCS(A,B)至少是4。但由于对角线上有V(MaxValue),所以本条对角线还不是解。同时,满足L(k-1,i-1) 第四步:如表格所示,计算相邻的第三条对角线 同理,这条对角线也不是解。把满足L(k-1,i-1) 第五步:如表格所示,计算相邻的第四条对角线 很遗憾,这个还不是解。满足L(k-1,i-1) 第六步:如表格所示,计算相邻的第五条对角线 这条对角线上的值都不是V(MaxValue)。因此,问题的解出来了。注意到这条对角线对应的k=5,因此LCS(A,B)=5。 在表格中,满足L(k-1,i-1) 按照这个依据,最长公共子序列的下标分别是 1;3;5;7;10 C=b1b3b5b7b10=41371 此时最佳匹配为: A:48123478_1 B:4411327431 1;3;6;7;10 C=b1b3b6b7b10=41271 此时最佳匹配为: A:481__23478_1 B:441132__7431 1;3;6;9;10 C=b1b3b6b9b10=41231 此时最佳匹配为: A:481__2__34781 B:441132743___1 1;3;5;8;10 C=b1b3b5b8b10=41341 此时最佳匹配为: A:48123__4781 B:441132743_1 1;3;6;8;10 C=b1b3b6b8b10=41241 此时最佳匹配为: A:481__234781 B:441132743_1 可以看出,Nakatsu算法很好的找到了所有的最长的公共子序列。但是,要找到所有的最长公共子序列,存储的空间是不会少于O(MN)的。因此,退而求其次,只求一个最长公共子序列。这个在空间的占用上是可以优化到O(M)的。 Nakatsu算法的时间分析:Nakatsu算法并没有把整个L矩阵计算出来,只是不停的尝试计算对角线,当遇到满足条件的对角线,就退出计算。假设LCS(A,B)=P。则可以判断出一共计算了M-P+1条对角线。而在每一条对角线的计算中,计算的次数满足下列的条件,假设计算第t条对角线 S=(L(1+t-1,1)-L(t-1,0))+(L(2+t-1,2)-L(1+t-1,1))……+(L(M+1-t,M)-L(M-t,M-1)) =L(M+1-t,M)-L(t-1,0) =L(M+1-t,M)≤V=N+1 故Nakatsu算法的时间占用为O((N+1)(M-P+1))。当P越是接近M,Nakatsu算法的速度越快。 没有优化过的Nakatsu算法占用空间O(MN)。因此,接下来的步骤就是对算法进行优化。前面提到,如果要计算出所有的最长公共子序列,空间是不可能压缩到线性空间的。因此,退而求其次,只求出一个最长公共子序列。通过观察,发现每一行第一个不是MaxValue的值组成的下标就是一个最长公共子序列。在本篇文章中就是1、3、6、9、10,对应的最长公共子序列是b1b3b6b9b10=41231。 接下来就是优化过程。注意到计算的过程是沿着对角线进行的。且每一个值的计算只和本条对角线和上一条对角线相关。因此,优化的思路就是把对角线转化为一维数组,也就是线性空间 第一步:初始化数组LL()和P(); LL(0)=0 LL(i)=V 1≤i≤M P(i)=V 1≤i≤M 此时,LL(0)表示L(0,0);LL(1)表示L(1,0);LL(2)表示L(2,1);…… 第二步:依次计算第一条对角线上的元素 用临时变量T计算L(1,1);T=F(L(0,0),L(1,0))=F(LL(0),LL(1))。注:F表示某种运算关系 将T的值赋给LL(1)。此时LL(1)表示LL(1,1),LL(2)表示L(2,1); 重复上面的计算,直到计算完本条对角线 如果是第k行的第一个不为V的值,将该值赋给P(k) 第三步:第一条对角线计算完之后,此时,LL(0)表示L(0,1);LL(1)表示L(1,1);LL(2)表示L(2,2);…… 如果,这条对角线不是解,重复第二步,计算下一条对角线。直到遇到解为止。 不过要注意的是。第i条对角线只有m-i+1个元素。所以只计算到LL(m-i+1)。 某条对角线,某个元素是V的话,则这条对角线之后的元素都是V,就不需要计算了。 贴上VB2008的代码。 用这个代码计算上面的示例时,得到的最长公共子序列是41231。在优化到线性空间的情况下,只能得出一个最长公共子序列。这也是Nakatsu算法的本质——计算最长公共子序列。至于要得到最佳匹配,还得继续研究。 代码如下: Public Class clsNakatsu Public Sub New(ByVal A As String, ByVal B As String) Public Function GetLCS() As String L(0) = -1 If P(j) = Integer.MaxValue AndAlso L(j) <> Integer.MaxValue Then P(j) = L(j) If L(j) = Integer.MaxValue Then Exit For If L(_A.Length - i) <> Integer.MaxValue Then For j = 1 To _A.Length - i Return tS.ToString Next Return ""
4
8
1
2
3
4
7
8
1
i=0
i=1
i=2
i=3
i=4
i=5
i=6
i=7
i=8
i=9
k=0
0
0
0
0
0
0
0
0
0
0
k=1
V
k=2
V
V
k=3
V
V
V
k=4
V
V
V
V
k=5
V
V
V
V
V
k=6
V
V
V
V
V
V
k=7
V
V
V
V
V
V
V
k=8
V
V
V
V
V
V
V
V
k=9
V
V
V
V
V
V
V
V
V
4
8
1
2
3
4
7
8
1
i=0
i=1
i=2
i=3
i=4
i=5
i=6
i=7
i=8
i=9
k=0
0
0
0
0
0
0
0
0
0
0
k=1
V
1
k=2
V
V
V
k=3
V
V
V
V
k=4
V
V
V
V
V
k=5
V
V
V
V
V
V
k=6
V
V
V
V
V
V
V
k=7
V
V
V
V
V
V
V
V
k=8
V
V
V
V
V
V
V
V
V
k=9
V
V
V
V
V
V
V
V
V
V
4
8
1
2
3
4
7
8
1
i=0
i=1
i=2
i=3
i=4
i=5
i=6
i=7
i=8
i=9
k=0
0
0
0
0
0
0
0
0
0
0
k=1
V
1
1
k=2
V
V
V
3
k=3
V
V
V
V
6
k=4
V
V
V
V
V
9
k=5
V
V
V
V
V
V
V
k=6
V
V
V
V
V
V
V
V
k=7
V
V
V
V
V
V
V
V
V
k=8
V
V
V
V
V
V
V
V
V
V
k=9
V
V
V
V
V
V
V
V
V
V
4
8
1
2
3
4
7
8
1
i=0
i=1
i=2
i=3
i=4
i=5
i=6
i=7
i=8
i=9
k=0
0
0
0
0
0
0
0
0
0
0
k=1
V
1
1
1
k=2
V
V
V
3
3
k=3
V
V
V
V
6
5
k=4
V
V
V
V
V
9
8
k=5
V
V
V
V
V
V
V
V
k=6
V
V
V
V
V
V
V
V
V
k=7
V
V
V
V
V
V
V
V
V
V
k=8
V
V
V
V
V
V
V
V
V
V
k=9
V
V
V
V
V
V
V
V
V
V
4
8
1
2
3
4
7
8
1
i=0
i=1
i=2
i=3
i=4
i=5
i=6
i=7
i=8
i=9
k=0
0
0
0
0
0
0
0
0
0
0
k=1
V
1
1
1
1
k=2
V
V
V
3
3
3
k=3
V
V
V
V
6
5
5
k=4
V
V
V
V
V
9
8
7
k=5
V
V
V
V
V
V
V
V
V
k=6
V
V
V
V
V
V
V
V
V
V
k=7
V
V
V
V
V
V
V
V
V
V
k=8
V
V
V
V
V
V
V
V
V
V
k=9
V
V
V
V
V
V
V
V
V
V
4
8
1
2
3
4
7
8
1
i=0
i=1
i=2
i=3
i=4
i=5
i=6
i=7
i=8
i=9
k=0
0
0
0
0
0
0
0
0
0
0
k=1
V
1
1
1
1
1
k=2
V
V
V
3
3
3
2
k=3
V
V
V
V
6
5
5
5
k=4
V
V
V
V
V
9
8
7
7
k=5
V
V
V
V
V
V
V
V
V
10
k=6
V
V
V
V
V
V
V
V
V
V
k=7
V
V
V
V
V
V
V
V
V
V
k=8
V
V
V
V
V
V
V
V
V
V
k=9
V
V
V
V
V
V
V
V
V
V
Private _A() As Char, _B() As Char
_A = A.ToCharArray
_B = B.ToCharArray
End Sub
Dim L(_A.Length) As Integer, P(_A.Length) As Integer
Dim i As Integer, j As Integer, T As Integer
For i = 1 To _A.Length
L(i) = Integer.MaxValue
P(i) = Integer.MaxValue
Next
For i = 0 To _A.Length - 1
For j = 1 To _A.Length - i
T = GetP(i + j - 1, L(j - 1), L(j))
If T <> Integer.MaxValue Then L(j) = T
Next
Dim tS As New System.Text.StringBuilder
tS.Append(_B(P(j)))
Next
End If
End Function
Private Function GetP(ByVal Index As Integer, ByVal StartP As Integer, ByVal EndP As Integer)
Dim i As Integer
If EndP = Integer.MaxValue Then EndP = _B.Length
For i = StartP + 1 To EndP - 1
If _A(Index) = _B(i) Then Return i
Next
Return Integer.MaxValue
End Function
End Class