一个给定序列的子序列是在该序列中删去若干元素后得到的序列。确切说,若给定序列 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={x1,x2,....,xm}和Y={y1,y2,...,yn},找出X和Y的最长公共子序列。
1.最长公共子序列的结构
穷举搜索法是最容易想到的算法,对X的所有子序列,检查它是否也是Y的子序列,从而确认它是否为X和Y的公共子序列。并且在检查过程中记录最长的公共子序列。X的所有子序列都检查后即可求出X和Y的最长公共子序列。但需要指数时间O(2m)。
最长公共子序列问题具有最优子结构性质。设序列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};
2,子问题的递归结构
由最长公共子序列问题的最优子结构性质可知,要找出X ={x1,x2,....,xm}和Y={y1,y2,...,yn}最长公共子序列,可以按一下方式递归进行:当 xm == yn 时,找出Xm-1 和 Yn-1 的最长公共子序列,然后在其尾部加上xm (==yn )即可得X和Y的最长公共子序列。当xm != yn 时,必须解决两个子问题,即找出Xm-1 和Y的最长公共子序列及Yn-1 和X的最长公共子序列。这两个公共子序列中较长者即为X和Y的最长公共子序列。
由此递归结构容易看到,最长公共子序列问题具有子问题重叠性质。例如,在计算X和Y的最长公共子序列时,可能要计算X和Yn-1 及Xm-1 和Y的最长公共子序列。而这两个子问题都包含一个公共子问题,即计算Xm-1 和Yn-1的最长公共子序列。
首先建立子问题最优值的递归关系。用c[i][j]记录序列Xi 和Yj 的最长公共子序列的长度。其中Xi ={x1,x2,....,xj};Yj={y1,y2,...,yj}。当i=0 或j =0时,空序列是Xi 和Yj 的最长公共子序列。故此时c[i][j]=0;其他情况下,由最优子结构性质可建立递归关系如下:
c[i][j] = 0 i=0,或 j=0; c[i][j] = c[i-1][j-1] +1 i,j>0;xi=yj; c[i][j] = max{c[i][j]-1],c[i-1][j]} i,j>0;xi !=yj
3 计算最优值i
直接利用递归式容易写出计算c[i][j]的递归算法,但其计算时间是随输入长度指数增长的。由于在所考虑的子问题空间中,总共有@(m*n)个不同的子问题,因此用动态规划算法自低向上计算最优值能提高算法的效率。
b[i][j] 记录 c[i][j]的值是由哪一个子问题的解得到的,这在构造最长公共子序列时要用到。问题最优值,即X和Y的最长公共子序列的长度记录与c[i][j];
void LCSlength(int m,int n,char *x,char *y,int **c,int **b) { int i,j; for(i=1; i <=m; i++) c[i][0]=0; for(i=1; i <= n; i++) c[0][i] =0; for(i=1; i =m;i++) for(j=1; j <=n;j++) { if(x[i] == y[j]){c[i][j] = c[i-1][j-1]+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;} else {c[i][j] = c[i][j-1];b[i][j] =3;} } }
LCSlength耗时O(mn)。
4。构造最长公共子序列
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);cout<<x[i];} else if(b[i][j] ==2) LCS(i-1,j,x,b); else LCS(j-1,i,x,b); }