1、问题的理解与描述
一个给定序列的子序列是在该序列中删去若干元素后得到的序列。确切地说,若给定序列X= { x1, x2,…, xm},则另一序列Z= {z1, z2,…, zk}是X的子序列是指存在一个严格递增的下标序列 {i1, i2,…, ik},使得对于所有j=1,2,…,k有Zj=Xij。例如,序列Z={B,C,D,B}是序列X={A,B,C,B,D,A,B}的子序列,相应的递增下标序列为{2,3,5,7}。
给定两个序列X和Y,当另一序列Z既是X的子序列又是Y的子序列时,称Z是序列X和Y的公共子序列。例如,若X= { A, B, C, B, D, A, B}和Y= {B, D, C, A, B, A},则序列{B,C,A}是X和Y的一个公共子序列,序列{B,C,B,A}也是X和Y的一个公共子序列。而且,后者是X和Y的一个最长公共子序列,因为X和Y没有长度大于4的公共子序列。
最长公共子序列问题:给定两个序列X= {x1, x2,…, xm}和Y= {y1, y2, … , yn},要求找出X和Y的一个最长公共子序列。
2、最长公共子序列的结构
穷举搜索法是最容易想到的方法。对X的所有子序列,检查它是否也是Y的子序列,从而确定它是否为X和Y的公共子序列。并且在检查的过程中记录最长的公共子序列。X的所有子序列都检查过后即可求出X和Y的最长公共子序列。X的每个子序列相应于下标集{1,2,...,m}的一个子集。因此共有2^m个不同子序列,因此,穷举法需要指数级别的运算时间。
事实上,最长子序列问题具有最优子结构的性质。
设序列X={x1, x2,…, xm}和Y={y1, y2, … , yn}的最长公共子序列为Z={z1, z2,…, zk}。则有:
(1)若xm=yn,则zk=xm=yn,且zk-1是xm-1和yn-1的最长公共子序列。
(2)若xm≠yn且zk≠xm,则Z是xm-1和Y的最长公共子序列。
(3)若xm≠yn且zk≠yn,则Z是X和Yn-1的最长公共子序列。
其中,Xm-1={x1, x2,…, xm-1},Yn-1={y1, y2, … , yn-1},Zk-1={z1, z2,…, zk-1}。
3、子问题的递归结构
4、计算最优值
<span style="font-size:24px;">LCS-LENGTH(X, Y, b)
1 m <-- length(X)
2 n <-- length(Y)
3 创建数组c
4 for i <-- 1 to m
5 do c[i][0] <-- 0
6 for j <-- 0 to n
7 do c[0][j] <-- 0
8 for i <-- 1 to m
9 do for j <-- 1 to n
10 do if xi=yj
11 then c[i][j]=c[i-1][j-1]+1
12 b[i][j] = 1
13 else if c[i-1][j]>=c[i][j-1]
14 then c[i][j]=c[i-1][j]
15 b[i][j] = 2
16 else c[i][j]=c[i][j-1]
17 b[i][j] = 3
18 return c</span>
/**
* @MethodName:lcsLength
* @Description: 构造c[m][n]
* @param x 序列X
* @param y 序列Y
* @param b 记录c[i][j]是由哪一个问题得到的,用于构造最优解
* @return
*/
public static int[][] lcsLength(char []x, char []y, int[][]b){
int m=x.length-1;//序列X长度
int n=y.length-1;//序列Y长度
int [][]c = new int[m+1][n+1];//创建数组c,用于保存序列Xi和Yj的最长公共子序列的长度
//当xi=0或yj=0时
for(int i=1; i<=m; i++){
c[i][0]=0;
}
for(int j=0; j<=n; j++){
c[0][j]=0;
}
//计算序列Xi和Yj的最长公共子序列的长度
for(int i=1; i<=m; i++){
for(int j=1; j<=n; j++){
if(x[i]==y[j]){//xi=yj
c[i][j]=c[i-1][j-1] + 1;
b[i][j] = 1;
}
//xi!=yj
else if (c[i-1][j]>=c[i][j-1]){
//序列Xi-1和Yj的最长公共子序列的长度大于等于序列Xi和Yj-1的最长公共子序列的长度
c[i][j]=c[i-1][j];
b[i][j] = 2;
}else{
//序列Xi-1和Yj的最长公共子序列的长度小于序列Xi和Yj-1的最长公共子序列的长度
c[i][j]=c[i][j-1];
b[i][j] = 3;
}
}
}
return c;
}
5、构造最优解
/**
* @MethodName:lcs
* @Description: 根据b的内容打印出Xi和Yj的最长公共子序列
* @param i 序列X的长度
* @param j 序列Y的长度
* @param x 序列X
* @param b 记录c[i][j]是由哪一个问题得到的数组
*/
public static void lcs(int i, int j, char []x, int [][]b){
if(i==0 ||j==0) return;
if(b[i][j]==1){
lcs(i-1, j-1, x, b);
System.out.print(x[i]);
}else if(b[i][j]==2){
lcs(i-1, j, x, b);
}else{
lcs(i, j-1, x, b);
}
}
在算法lcs中,每一次递归调用使i或j减1,因此算法的计算时间为O(m+n)。6、算法的改进
<span style="font-size:24px;">/**
* @MethodName:lcsEx
* @Description: 改进后的lcs
* @param i 序列X的长度
* @param j 序列Y的长度
* @param x 序列X
* @param y 序列Y
* @param c 序列Xi和Yj的最长公共子序列的长度数组
*/
public static void lcsEx(int i, int j, char []x, char []y, int [][]c){
if(i==0 ||j==0) return;
if(x[i] == y[j]){
lcsEx(i-1, j-1, x, y, c);
System.out.print(x[i]);
}else if(c[i-1][j]>=c[i][j-1]){
lcsEx(i-1, j, x, y, c);
}else{
lcsEx(i, j-1, x, y, c);
}
}</span>
例如:对X=<A,B,C,B,D,A,B>和Y=<B,D,C,A,B,A>算法产生的表c以及回溯构造最优解的过程如下: