最长公共子序列计算步骤:
第一步:先计算最长公共子序列的长度。
第二步:根据长度,然后通过回溯求出最长公共子序列。
现有两个序列X={x1,x2,x3,...xi},Y={y1,y2,y3,....,yi},
一共有两种情况,一种是
x1 = = y1,则长度为lcs({x2,x3,x4....xi},{y2,y3,....,yi})+1
若不等,则递归计算取
lcs({x1,x3,x4....xi},{y2,y3,....,yi})和 lcs({x2,x3,x4....xi},{y1,y2,y3,....,yi})之中较大的值作为公共子序列的长度。
递推方程为:(此图片来源于网络)
动态规划最重要最核心的就是求出递推方程,有了递推方程,我们可以非常容易的利用递归写出代码:
public class LCS {
String x = "122f35624";
char [] m = x.toCharArray();
String y = "2135ff4";
char [] n = y.toCharArray();
int [][] a = new int[x.length()][y.length()];//用来保存中间计算的状态
public static void main(String[] args) {
LCS lcs = new LCS();
lcs.init();
int result = lcs.solve(0,0);
System.out.println();
System.out.println(result);
lcs.resolvePath(result);
}
public void init(){
for (int i=0; i<x.length(); i++)
for (int j=0; j<y.length(); j++){
a[i][j] = -1;
}
}
public int solve(int i,int j){//记忆化搜索
if (i==x.length()|| j==y.length())
return 0;
if (a[i][j] >=0)//此处为了提高效率,防止重复计算,当计算过时直接返回值,不再重复计算
return a[i][j];
if (m[i]==n[j]){
return a[i][j] = solve(i+1,j+1)+1;
}
else
return a[i][j] = Math.max(solve(i+1,j),solve(i,j+1));
}
/**
* 打印最长公共字串
* @param result
*/
public void resolvePath(int result){
int x = 0;
int y = 0;
char [] path = new char[result];
int count = 0;
while (x <m.length && y <n.length) {
if (m[x] == n[y]) {
path[count++] = m[x];
x++;
y++;
}
else if (x<m.length-1&&a[x][y] == a[x+1][y]){
x++;
}else if (y<n.length-1 &&a[x][y] == a[x][y+1])
y++;
}
for (int i=0; i<path.length; i++)
System.out.print(path[i]);
}
}
除了利用递归方法解决上述问题外,还可以采用递推法解决。将上述函数换成数组
设一个c[i,j]: 保存Xi与Yj的LCS的长度。(此图片来源于网络)
对应代码如下:
/**
* 递推法计算最长公共子序列
* @return 最长公共子序列长度
*/
public int lcs(){
for (int i=0; i<m.length; i++){//0行0列初始化为0
a[i][0] = 0;
a[0][i] = 0;
}
for (int i=1; i<=m.length; i++)
for (int j=1; j<=n.length; j++){
if (m[i-1] == n[j-1])
a[i][j] = a[i-1][j-1] + 1;
else
a[i][j] = Math.max(a[i][j-1],a[i-1][j]);
}
return a[m.length][n.length];
}
打印公共子序列函数与递归法相同。
程序运行结果如下: