/**
* 动态规划之最长公共子序列问题:给定一个子序列 X = <x1,x2,...xm>,另一个子序列Z = <z1,z2,...zk>是X的子序列,如果存在X的一个严格递增下标序列
* <i1,i2,...ik>,使得对于所有j = 1,2,...k,有xij = zj。如Z=<B,C,D,B>是X=<A,B,C,B,D,A,B>子序列,对应下标i = <2,3,5,7>
* LCS问题:给定序列X=<x1,x2,...xn>和Y=<y1,y2...ym>,找出X和Y的最长公共子序列
* 设Z<z1,z2,...zk>是X和Y的任一个最长公共子序列,那么:
* 1) 若xn = ym,则zk = xn = ym 而且<z1,z2,...zk-1>是Xn-1和Ym-1的最长公共子序列
* 2) 若xn != ym 则zk!=xn 蕴含Z是Xn-1和Y的一个LCS
* 3) 若ym != xn 则zk!= ym蕴含Z是x和Ym-1的一个LCS
*
* 基于以上结论,可以得出一个递归解:
* 定义c[i][j]是序列Xi和Yj的LCS的长度则:
* --> 0 if i = 0 或者 j = 0
* c[i][j] = --> c[i-1][j-1] + 1 如果i>0,j>0,xi=yj
* --> max{c[i-1][j],c[i][j-1]} 如果i>0,j>0,xi != yj
* @author song
*
*/
/** * 计算x和y的公共子序列长度表 * @param x * @param y * @return */ public static int[][] calculateCommonSubsequenceNum(char[] x, char[] y){ int n = x.length; int m = y.length; //记录各个LCS长度的二维表 int[][] c = new int[n+1][m+1]; //初始化:c[i][j] = 0 当i=0 or j=0 for(int i = 0; i <= m; ++i) c[0][i] = 0; for(int j = 0; j <= n; ++j) c[j][0] = 0; //注意c[1][1]记录的是 x[0]和y[0]的公共子序列长度,因为x和y的第一个元素不是x[1]和y[1],而是x[0]和y[0] for( int i = 1; i <= n; ++i){ for(int j = 1; j <= m; ++j){ if( x[i-1] == y[j-1] ) c[i][j] = c[i-1][j-1] + 1; else{ c[i][j] = Math.max(c[i-1][j], c[i][j-1]); } } } return c; }/**
* 找出一个最长公共子序列 * 找出LCS依赖于 {@link #calculateCommonSubsequenceNum(char[], char[])}的LCS长度序列表 * 根据最初分析的三个结论 * 1)若Z是X和Y的LCS,若xn=ym,则zk = xn = ym即X和Y的最后一个字符肯定出现在LCS中 * 2)若xn != ym,则Z肯定是 Xn-1和Ym的LCS 以及 Xn和Ym-1的LCS中更长得那一个序列,也就是说当xn!=ym时,可以转化为找 Xn-1和Ym的LCS/ Xn和Ym-1的LCS * 更进一步,若c[n-1][m] > c[n][m-1],找Xn-1和Ym的LCS即可,因为他更长,反之找Xn和Ym-1的LCS * 因此,从后向前扫描x和y序列,碰到相同则将该字符放入LCS, * 若不相同,则比较c[n-1][m] > c[n][m-1],根据他们的值比较xn-1和ym 或者 xn和ym-1 * * @param x * @param y * @return */ public static List<Character> findLongestLCS(char[] x,char[] y){ List<Character> result = new LinkedList<Character>(); int c[][] = calculateCommonSubsequenceNum(x, y); int n = x.length - 1; int m = y.length - 1; while( n >= 0 && m >= 0){ if(x[n] == y[m]){ result.add(0, x[n]); n--;m--; }else{ if(c[n][m+1] > c[n+1][m]) n--; else m--; } } return result; }
测试代码:
public static void main(String[] args) { char[] x = "10010101".toCharArray(); char[] y = "010110110".toCharArray(); List<Character> r = findLongestLCS(x, y); System.out.println("The longest LCS:" + r.toString()); }
result:The longest LCS:[0, 1, 0, 1, 0, 1]