最长公共子序列
问题描述
给定两个序列X={A,B,C,B,D,A,B}和Y={B,D,C,A,B,A},当另一序列Z={B,D}既是X的子序列又是Y的子序列时,称Z是序列X和Y的公共子序列。Z={B,C,B,A}是比{B,D}更长的子序列,它的长度为4,因为X和Y没有长度大于4的公共子序列,所以Z ={B,C,B,A}是X和Y的最长公共子序列。
最长公共子序列问题(Longest Common Subsequence, LCS):给定两个序列X={x1, x2, …, xm}和Y={y1, y2, …, yn},找出X和Y的最长公共子序列Z={z1, z2, …, zk} 。
解决方案
1.找出最长公共子序列问题最优解性质,并刻划其结构特征。
1)若xm=yn = zk,则Zk-1是Xm-1和Yn-1的最长公共子序列。
2)若xm≠yn且zk≠xm,则Z是Xm-1和Y的最长公共子序列。
3)若xm≠yn且zk≠yn,则Z是X和Yn-1的最长公共子序列。
- 子问题的递归结构
由最长公共子序列问题的最优子结构性质建立子问题最优值c[i][j]的递归关系。当i=0或j=0时,空序列是Xi和Yj的最长公共子序列。其它情况下,由最优子结构性质可建立递归关系如下:
- 计算最优值
package dynamic; /* * 给定x[0:m]和y[0:n]字符序列,求两者的最长公共子序列,公共子序列长度记录在二维数组c[0:m][0:n]内, * 则最长为c[m][n],最优解的寻找轨迹记录在二维数组b[0:m][0:n]内,通过对应x的下标,将公共子序列输出 * 递归的出口在i=0或y=0 * c[i][j]下标从1开始 * x[i],y[j]下标从0开始,所以遍历x,y时,x[i-1],y[j-1]表示 */ public class Longest_common_subsqence { public static int lcsLength(char[] x,char[] y,int[][] b) { //x,y为给定的字符序列,b用来记录公共子序列的寻找轨迹 int m=x.length; int n=y.length; int[][] c=new int[m+1][n+1];//将当前位置的公共子序列长度情况,结果记录在表格里,行列数加1用来保存0 for(int i=0;i<=m;i++) {c[i][0]=0;} //如果y为空序列,那么他们的公共子序列也为空序列 for(int j=0;j<=n;j++) {c[0][j]=0;} //如果x为空序列,那么他们的公共子序列也为空序列 for(int i=1;i<=m;i++) //当x和y都不为空序列时,开始遍历各自每一个是否相同 for(int j=1;j<=n;j++) { if(x[i-1]==y[j-1]) { //如果x和y中有相同的元素,那么最长公共子序列长度加1 c[i][j]=c[i-1][j-1]+1; b[i][j]=1; //b[i][j]=1代表两元素相同,方便记录最优解 } else if(c[i-1][j]>=c[i][j-1]) { //如果当前位置的上面的元素大于等于它左边的元素, c[i][j]=c[i-1][j]; //那么这个位置的值为上面元素的值所表示的公共子序列长度 b[i][j]=2; //b[i][j]=2代表上面元素所代表的长度大于等于左边元素所代表的长度 } else { //如果当前位置的上面元素小于它的左边元素, c[i][j]=c[i][j-1]; //那么这个位置的值为左边元素的值所代表的公共子序列长度 b[i][j]=3; //b[i][j]=3代表左边元素所代表的长度大于上面元素所代表的长度 } } return c[m][n]; //当遍历到二维表格的最后位置时,此时的值即为最长公共子序列 } public static void lcs(int i,int j,char[] x,int[][] b) { //lcs方法用来输出最优解,即最长公共子序列 if(i==0||j==0) return;//递归的出口 if(b[i][j]==1) { lcs(i-1,j-1,x,b); //c[i][j]=c[i-1][j-1]+1 System.out.println(x[i-1]); //输出当前位置的相同字符 } else if(b[i][j]==2) lcs(i-1,j,x,b); //c[i][j]=c[i-1][j] else lcs(i,j-1,x,b); //c[i][j]=c[i][j-1] } public static void main(String[] args) { // TODO Auto-generated method stub char[] x= {'A','B','C','B','D','A','B'}; char[] y= {'B','D','C','A','B','A'}; int[][] b=new int[x.length+1][y.length+1]; //行列数都各自加1,因为需要给空序列留一个位置来表示 lcsLength(x,y,b); lcs(x.length,y.length,x,b); } }
4.演算过程
反思总结
一开始代码打好以后,发现总是报数组越界的错误,先是发现c[i][j]和b[i][j]的行列数要比x[]和y[]多1,来保存空序列,但是为了一致性,x和y的下标要从c,b下标减1表示。
这道题的解法通过用b[][]来记录演算轨迹,不同的值表示不同的情况,遇到1就根据i-1的值从x[]中将公共序列输出。
tip