最长公共子序列
第一节、问题描述
什么是最长公共子序列呢?好比一个数列 S,如果分别是两个或多个已知数列的子序列,且是所有符合此条件序列中最长的,则S 称为已知序列的最长公共子序列。
举个例子,如:有两条随机序列,如 1 3 4 5 5 ,and 2 4 5 5 7 6,则它们的最长公共子序列便是:4 5 5。
注意最长公共子串(Longest CommonSubstring)和最长公共子序列(LongestCommon Subsequence, LCS)的区别:
子串(Substring)是串的一个连续的部分,子序列(Subsequence)则是从不改变序列的顺序,而从序列中去掉任意的元素而获得的新序列;
更简略地说,前者(子串)的字符的位置必须连续,后者(子序列LCS)则不必。
比如字符串acdfg同akdfc的最长公共子串为df,而他们的最长公共子序列是adf。
LCS可以使用动态规划法解决。下文具体描述。
第二节、LCS问题的解决思路
- 穷举法
解最长公共子序列问题时最容易想到的算法是穷举搜索法,即对X的每一个子序列,检查它是否也是Y的子序列,从而确定它是否为X和Y的公共子序列,并且在检查过程中选出最长的公共子序列。X和Y的所有子序列都检查过后即可求出X和Y的最长公共子序列。X的一个子序列相应于下标序列{1, 2, …, m}的一个子序列,因此,X共有2m个不同子序列(Y亦如此,如为2^n),从而穷举搜索法需要指数时间(2^m * 2^n)。
- 动态规划算法
事实上,最长公共子序列问题也有最优子结构性质。
记:
Xi=﹤x1,⋯,xi﹥即X序列的前i个字符 (1≤i≤m)(前缀)
Yj=﹤y1,⋯,yj﹥即Y序列的前j个字符 (1≤j≤n)(前缀)
假定Z=﹤z1,⋯,zk﹥∈LCS(X , Y)。
子问题的递归结构
由最长公共子序列问题的最优子结构性质可知,要找出X=<x1, x2, …, xm>和Y=<y1, y2, …, yn>的最长公共子序列,可按以下方式递归地进行:
当xm=yn时,找出Xm-1和Yn-1的最长公共子序列,然后在其尾部加上xm(=yn)即可得X和Y的一个最长公共子序列。
当xm≠yn时,必须解两个子问题,即找出Xm-1和Y的一个最长公共子序列及X和Yn-1的一个最长公共子序列。
这两个公共子序列中较长者即为X和Y的一个最长公共子序列。
由此递归结构容易看到最长公共子序列问题具有子问题重叠性质。例如,在计算X和Y的最长公共子序列时,可能要计算出X和Yn-1及Xm-1和Y的最长公共子序列。而这两个子问题都包含一个公共子问题,即计算Xm-1和Yn-1的最长公共子序列。
与矩阵连乘积最优计算次序问题类似,我们来建立子问题的最优值的递归关系。
用c[i,j]记录序列Xi和Yj的最长公共子序列的长度。其中Xi=<x1, x2, …, xi>,Yj=<y1, y2, …, yj>。
当i=0或j=0时,空序列是Xi和Yj的最长公共子序列,故c[i,j]=0。
其他情况下,由定理可建立递归关系如下:
我们用c[i][j]记录X[0~i]与Y[0~j] 的LCS 的长度,那么在计算c[i,j]之前,c[i-1][j-1],c[i-1][j]与c[i][j-1]均已计算出来。
此时我们根据X[i] = Y[j]还是X[i] != Y[j],就可以计算出c[i][j]。问题的递归式写成:
例如,设所给的两个序列为X=<A,B,C,B,D,A,B>和Y=<B,D,C,A,B,A>。由算法LCS_LENGTH和LCS计算出的结果如下图所示:
1、字符相同,则指向左上,并加1
2、字符不同,则指向左边或者上边较大的那个
由于每次调用至少向上或向左(或向上向左同时)移动一步,故最多调用(m * n)次就会遇到i = 0或j = 0的情况,此时开始返回。返回时与递归调用时方向相反,步数相同,故算法时间复杂度为Θ(m * n)。[这个不太确定]
/**
* 最长公共子序列(LongestCommon Subsequence, LCS)
* Eg.X=ABCBDAB和Y=BDCABA
* 输出最长公共子序列长度为4,BCBA
* */
public class LCS {
public static int[][] longestCS(String x,String y){
int m=x.length();
int n=y.length();
int opt[][]=new int[m+2][n+2];//用c[i][j]记录X[0~i]与Y[0~j] 的LCS 的长度
for(int i=0;i<=m+1;i++){
opt[i][0]=0;
opt[i][n+1]=0;//防止之后输出的时候数组越界,把四周都包上0
}
for(int j=0;j<=n;j++){
opt[0][j]=0;
opt[m+1][j]=0;
}
for(int j=1;j<=n;j++){
for(int i=1;i<=m;i++){
if(x.charAt(i-1)==y.charAt(j-1)){
opt[i][j]=opt[i-1][j-1]+1;
}else{
opt[i][j]=(opt[i-1][j]>opt[i][j-1]?opt[i-1][j]:opt[i][j-1]);
}
}
}
return opt;
}
public static void printLCS(int opt[][],String x,String y,int i,int j){
if(i==(x.length()+1)||j==(y.length()+1)){return;}
if(x.charAt(i-1)==y.charAt(j-1)){
System.out.print(x.charAt(i-1));
printLCS(opt,x,y,i+1,j+1);
}else if(opt[i][j+1]>=opt[i+1][j]){
printLCS(opt,x,y,i,j+1);
}else{printLCS(opt,x,y,i+1,j);}
}
public static void main(String[] args){
String x="ABCBDAB";
String y="BDCABA";
int m=x.length();
int n=y.length();
int opt[][]=new int[m+1][n+1];
opt=longestCS(x,y);
printLCS(opt,x,y,1,1);
}
}
"1AB2345CD",9,"12345EF",7
返回:4
//最长公共子串
import java.util.*;
public class LCSub {
public static int findLongest(String A, int n, String B, int m) {
// write code here
int opt[][]=new int[n][m];
int length=0;
int end=0;
for(int j=0;j<m;j++){
for(int i=0;i<n;i++){
if(A.charAt(i)==B.charAt(j)){
if(i==0||j==0){
opt[i][j]=1;
}else{
opt[i][j]=opt[i-1][j-1]+1;
}
if(opt[i][j]>length){
length=opt[i][j];end=i;
}
}
}
}
return (A.substring(end-length+1,end+1)).length();
}
public static void main(String[] args){
String A="1AB2345CD";
String B="12345EF";
int n=9;
int m=7;
System.out.println(findLongest(A,n,B,m));
}
}