最长公共子序列 LCS
问题
- 给定2个序列X,Y。Z是2个序列的最长子序列,可以不连续,可能不唯一。
- 比如说 X : A C D G E,Y : A D G E 则最长LCS(longest common sequence)为:A D E or A G E
解题步骤
- 输入2个字符串 s1 s2 ,设MaxLen(i, j)表示s1左边共i个字符形成的字串与s2左边共j个字符形成的字串的最长公共子序列长度。i和j从0开始算。
- 假设len1 = str(s1),len2 = str(s2),则就是求MaxLen(len1,len2)。
思路:2个序列有下面3种情况
1. 2个序列之中有一个0序列,则LCS = 0
2. 2个m,n长的序列,他们的最后一个数据相同,则求前m-1, n-1的LCS + 最后一项(如果求长度就+1)
3. 如果最后一项不同,则m ≠ n,有可能m>n or m<n,所以求MAX(LCS(n,m-1), LCS(m,n-1))
显然
MaxLen(n, 0) = 0 (n=0....len1)
MaxLen(0, n) = 0 (n=0....len2)
递推公式:
if s1[i-1] == s2[j-1]
MaxLen(i, j) = MaxLen(i-1, j-1) + 1
else
MaxLen(i, j) = max{ MaxLen(i, j-1),MaxLen(i-1, j)}
求解LCS长度递推的伪码表示
- 从已知条件推未知条件,只要先求出初试状态,此题为先求出MaxLen(0, n) and MaxLen(n, 0)。
- 用二维数组保存s1,s2。
- JAVA代码查看<<算法设计与分析第四版>>Page 6 0。
int MaxLen[MAX][MAX]
int main(){
while(cin >> s1 >> s2){
int len1 = str(s1)
int len2 = str(s2)
for(int i=0; i<= len1; i++)
MaxLen[i][0] = 0
for(int j=0; j<= len2; j++)
MaxLen[0][j]= 0
}
int i,j
for(i = 1; i<= len1; i++)
for(j = 1; j<= len2; j++)
if( s1[i-1] == s2[j-1] )
MaxLen[i][j] = MaxLen[i-1][j-1] + 1
else
MaxLen[i][j] = max{ MaxLen[i][j-1],MaxLen[i-1][j] }
cout << MaxLen[len1][len2]
}
递推伪代码的图像解释(重点)
- i-1处即是判断 s1[i-1] 是否等于 s2[j-1],如果相等(最长公共子序列的长度)就是绿色箭头所指位置的值+1,如果不等就是红色箭头所指位置的值中最大的。
- 重点:其实是把所有可能都求解出来的。
求解LCS串
- 之前求出的是LCS的长度,下面求LCS的具体数据,即求出子串。
- 用标记函数LCS确定路径,二维数组B的值确定状态。
int B[][]
for(i = 1; i<= len1; i++)
for(j = 1; j<= len2; j++)
if( s1[i-1] == s2[j-1] )
MaxLen[i][j] = MaxLen[i-1][j-1] + 1
B[i][j] = 1
if(Max[i][j-1] > MaxLen[i-1][j])
MaxLen[i][j] = Max[i][j-1]
B[i][j] = 2
if(Max[i][j-1] < MaxLen[i-1][j])
MaxLen[i][j] = Max[i-1][j]
B[i][j] = 3
int LCS(int i, int j, char s1[], int B[][]){
if (i==0 || j==0)
return
if (B[i][j] == 1)
LCS(i-1, j-1, s1, B)
System.out.print(s1[i])
******
if (B[i][j] == 2)
LCS(i, j-1, s1, B)
if (B[i][j] == 3)
LCS(i-1, j, s1, B)
}
JAVA代码表示
时间复杂度
- 求解LCS的长度时间复杂度是判断2个子串是否相等,需要遍历2层,所以是O(mn)。
- 求解LCS串是O(m+n),因为每次递归i-1,直到0为止。
应用
- 自然界中检测2个DNA链中有没有公共的,判断它们DNA的相似度,越长越相似。