最长公共子序列问题(LCS)
问题描述:
所谓子序列是子序列是从最初序列通过去除某些元素但不破坏余下元素的相对位置而形成的新序列。形如X=<x1, x2, x3………, xm>这个序列,对于Z=<xi1, xi2 , xi3 ………, xik>就是X的子序列。e.g. X=<A,B,C,D,E,F,G,H> Z=<A,D,F>就是X的一个子序列。
公共子序列就是多个序列(如X,Y),现如果存在Z,且Z是X的子序列,Z又是Y的子序列,则Z就是X和Y的公共子序列。
最长公共子序列的概念,从以上关于子序列和公共子序列的概念中,我们可以很简单的知道,最长公共子序列就是多个序列的公共子序列中最长的一个。e.g. X=<A,B,C,B,D,A,B>, Y=<B,D,C,A,B,A>则Z1=<B,C,B,A>, Z2=<B,C,A,B>, Z3=<B,D,A,B>都是X和Y的最长公共子序列,长度为4,而且我们从此可以得知最长公共子序列往往不是唯一的哟。
问题求解:
对此问题用的是DP(动态规划)的方法解决的,即使可以一眼看出来用brute-force(蛮力)依次匹配。但是这个蛮力的效率实在是不行啊。下面我们就开始对LCS问题的DP解法:
分析:
记Xi=﹤x1,…,xi﹥即X序列的前i个字符 (1≤i≤m)(前缀)Yj=﹤y1,…,yj﹥即Y序列的前j个字符 (1≤j≤n)(前缀)。假定Z=﹤z1,…,zk﹥∈LCS(X , Y)。
若xm=yn(最后一个字符相同),则不难用反证法证明:该字符必是X与Y的任一最长公共子序列Z(设长度为k)的最后一个字符,即有zk = xm = yn ,且显然有Zk-1∈LCS(Xm-1 , Yn-1),即Z的前缀Zk-1是Xm-1与Yn-1的最长公共子序列。
若xm≠yn,则亦不难用反证法证明:要么Z∈LCS(Xm-1, Y),要么Z∈LCS(X , Yn-1)。由于zk≠xm与zk≠yn其中至少有一个必成立因此:若zk≠xm则有Z∈LCS(Xm-1 , Y),若zk≠yn 则有Z∈LCS(X , Yn-1)。
∴若xm=yn,则问题化归成求Xm-1与Yn-1的LCS,即(LCS(X , Y)的长度等于LCS(Xm-1 , Yn-1)的长度加1)
若xm≠yn,则问题化归成求Xm-1与Y的LCS及X与Yn-1的LCSLCS(X , Y)的长度为:Max {LCS(Xm-1 , Y)的长度, LCS(X , Yn-1)的长度}
这两个问题不是相互独立的:
∵两者都需要求LCS(Xm-1,Yn-1)的长度,因而具有重叠性。此外,两个序列的LCS中包含了两个序列的前缀的LCS,故问题具有最优子结构性质 考虑用动态规划法。
现引进一个二维数组C,
用C[i,j]记录Xi与Yj的LCS的长度 如果我们是自底向上进行递推计算,那么在计算C[i,j]之前,
C[i-1,j-1], C[i-1,j]与C[i,j-1]均已计算出来。此时根据X[i]=Y[j]还是X[i]Y[j],就可以计算出C[i,j]:
若X[i]=Y[j],则执行C[i,j]←C[i-1,j-1]+1;
若X[i]≠Y[j],进行下述判断:
若C[i-1,j]≥C[i,j-1]则C[i,j]取C[i-1,j];
否则C[i,j]取C[i,j-1]。
即有
为了构造出LCS,使用一个mn的二维数组b,b[i,j]记录C[i,j]是通过哪一个子问题的值求得的,以决定搜索的方向:
若X[i]=Y[j],则b[i,j]中记入“↖”;
否则
若C[i-1,j]=C[i,j-1],则b[i,j]中记入“↑或←”
若C[i-1,j]>C[i,j-1],则b[i,j]中记入“↑”;
若C[i-1,j] < C[i,j-1],则b[i,j]中记入“←”;
这样就可以记录下来寻找LCS的路径了。
代码示例:
用C语言对LCS问题进行了实现,基本上得到了预期结果。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_LEN 512
#define TRACE_END 0
#define UP 1
#define LEFT 2
#define UP_AND_LEFT 3
#define UP_OR_LEFT 4
typedef char **(sub_strs_ptr);//to store lcs resualts
int is_exited_sstrs(sub_strs_ptr sstrs,int sstrs_len,char *new_str){
int i;
for(i=0;i<sstrs_len;i++)
if(!strcmp(*(sstrs+i),new_str)) return 1;
return 0;
}
void display_lcs(int *trace_tab,char* str,int n,int i,int j,char* out,int k,sub_strs_ptr sstrs,int *sstrs_num)
{
if(trace_tab[i*(n+1)+j] == TRACE_END) {
if(!is_exited_sstrs(sstrs,*sstrs_num,out)){
strcpy(*(sstrs+*sstrs_num),out);
(*sstrs_num)++;
printf("%d:%s\n",*sstrs_num,out);
}
return;
}
if(trace_tab[i*(n+1)+j] == UP_AND_LEFT)
{
out[k--]=str[i-1];//got a char
display_lcs(trace_tab,str,n,i-1,j-1,out,k,sstrs,sstrs_num);
}
else if(trace_tab[i*(n+1)+j] == UP)
display_lcs(trace_tab,str,n,i-1,j,out,k,sstrs,sstrs_num);
else if(trace_tab[i*(n+1)+j] == LEFT)
display_lcs(trace_tab,str,n,i,j-1,out,k,sstrs,sstrs_num);
else
{//UP OR LEFT
display_lcs(trace_tab,str,n,i-1,j,out,k,sstrs,sstrs_num);
display_lcs(trace_tab,str,n,i,j-1,out,k,sstrs,sstrs_num);
}
}
int main()
{
int i,j;
int m,n,*lcs_tab,*trace_tab,lcs_len,sstrs_num;
char str_a[MAX_LEN],str_b[MAX_LEN],out[MAX_LEN];
sub_strs_ptr sstrs;
scanf("%s%s",str_a,str_b);
m=strlen(str_a);
n=strlen(str_b);
lcs_tab=(int*)malloc((m+1)*(n+1)*sizeof(int));
trace_tab=(int*)malloc((m+1)*(n+1)*sizeof(int));/*store by row*/
if(!lcs_tab || !trace_tab) exit(-2);
for(i=0;i<=m;i++) { lcs_tab[(n+1)*i]=0; trace_tab[(n+1)*i]=TRACE_END;}
for(i=0;i<=n;i++) { lcs_tab[i]=0; trace_tab[i]=TRACE_END;}
for(i=1;i<=m;i++)
for(j=1;j<=n;j++)
{
if(str_a[i-1] == str_b[j-1])
{
lcs_tab[i*(n+1)+j]=lcs_tab[(i-1)*(n+1)+(j-1)]+1;
trace_tab[i*(n+1)+j]=UP_AND_LEFT;
}
else
{
if(lcs_tab[(i-1)*(n+1)+j] == lcs_tab[i*(n+1)+j-1])
{
lcs_tab[i*(n+1)+j]=lcs_tab[(i-1)*(n+1)+j];
trace_tab[i*(n+1)+j]=UP_OR_LEFT;
}
else if(lcs_tab[(i-1)*(n+1)+j] > lcs_tab[i*(n+1)+j-1])
{
lcs_tab[i*(n+1)+j]=lcs_tab[(i-1)*(n+1)+j];
trace_tab[i*(n+1)+j]=UP;
}
else
{
lcs_tab[i*(n+1)+j]=lcs_tab[i*(n+1)+j-1];
trace_tab[i*(n+1)+j]=LEFT;
}
}
}
printf("lcs_tab:\n");
for(i=0;i<=m;i++){
for(j=0;j<=n;j++)
printf("%-2d",lcs_tab[i*(n+1)+j]);
printf("\n");
}
printf("\ntarce_tab:\n");
for(i=0;i<=m;i++){
for(j=0;j<=n;j++){
switch(trace_tab[i*(n+1)+j]){
case TRACE_END:printf("● ");break;
case UP:printf("↑ ");break;
case LEFT:printf("← ");break;
case UP_AND_LEFT:printf("↖ ");break;
case UP_OR_LEFT:printf("←↑ ");break;
}
//printf("%-2d",trace_tab[i*(n+1)+j]);
}
printf("\n");
}
lcs_len=lcs_tab[m*(n+1)+n];
printf("\nlcs's len = %d\n",lcs_len);
int min_len=m>n?n:m;
sstrs=(char**)malloc(min_len*sizeof(char*));
for(i=0;i<min_len;i++)
*(sstrs+i)=(char*)malloc(MAX_LEN*sizeof(char));
sstrs_num=0;
if(lcs_len != 0){
out[lcs_len]='\0';
display_lcs(trace_tab,str_a,n,m,n,out,lcs_len-1,sstrs,&sstrs_num);
}
printf("in total %d kinds lcs!\n",sstrs_num);
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_LEN 512
#define TRACE_END 0
#define UP 1
#define LEFT 2
#define UP_AND_LEFT 3
#define UP_OR_LEFT 4
typedef char **(sub_strs_ptr);//to store lcs resualts
int is_exited_sstrs(sub_strs_ptr sstrs,int sstrs_len,char *new_str){
int i;
for(i=0;i<sstrs_len;i++)
if(!strcmp(*(sstrs+i),new_str)) return 1;
return 0;
}
void display_lcs(int *trace_tab,char* str,int n,int i,int j,char* out,int k,sub_strs_ptr sstrs,int *sstrs_num)
{
if(trace_tab[i*(n+1)+j] == TRACE_END) {
if(!is_exited_sstrs(sstrs,*sstrs_num,out)){
strcpy(*(sstrs+*sstrs_num),out);
(*sstrs_num)++;
printf("%d:%s\n",*sstrs_num,out);
}
return;
}
if(trace_tab[i*(n+1)+j] == UP_AND_LEFT)
{
out[k--]=str[i-1];//got a char
display_lcs(trace_tab,str,n,i-1,j-1,out,k,sstrs,sstrs_num);
}
else if(trace_tab[i*(n+1)+j] == UP)
display_lcs(trace_tab,str,n,i-1,j,out,k,sstrs,sstrs_num);
else if(trace_tab[i*(n+1)+j] == LEFT)
display_lcs(trace_tab,str,n,i,j-1,out,k,sstrs,sstrs_num);
else
{//UP OR LEFT
display_lcs(trace_tab,str,n,i-1,j,out,k,sstrs,sstrs_num);
display_lcs(trace_tab,str,n,i,j-1,out,k,sstrs,sstrs_num);
}
}
int main()
{
int i,j;
int m,n,*lcs_tab,*trace_tab,lcs_len,sstrs_num;
char str_a[MAX_LEN],str_b[MAX_LEN],out[MAX_LEN];
sub_strs_ptr sstrs;
scanf("%s%s",str_a,str_b);
m=strlen(str_a);
n=strlen(str_b);
lcs_tab=(int*)malloc((m+1)*(n+1)*sizeof(int));
trace_tab=(int*)malloc((m+1)*(n+1)*sizeof(int));/*store by row*/
if(!lcs_tab || !trace_tab) exit(-2);
for(i=0;i<=m;i++) { lcs_tab[(n+1)*i]=0; trace_tab[(n+1)*i]=TRACE_END;}
for(i=0;i<=n;i++) { lcs_tab[i]=0; trace_tab[i]=TRACE_END;}
for(i=1;i<=m;i++)
for(j=1;j<=n;j++)
{
if(str_a[i-1] == str_b[j-1])
{
lcs_tab[i*(n+1)+j]=lcs_tab[(i-1)*(n+1)+(j-1)]+1;
trace_tab[i*(n+1)+j]=UP_AND_LEFT;
}
else
{
if(lcs_tab[(i-1)*(n+1)+j] == lcs_tab[i*(n+1)+j-1])
{
lcs_tab[i*(n+1)+j]=lcs_tab[(i-1)*(n+1)+j];
trace_tab[i*(n+1)+j]=UP_OR_LEFT;
}
else if(lcs_tab[(i-1)*(n+1)+j] > lcs_tab[i*(n+1)+j-1])
{
lcs_tab[i*(n+1)+j]=lcs_tab[(i-1)*(n+1)+j];
trace_tab[i*(n+1)+j]=UP;
}
else
{
lcs_tab[i*(n+1)+j]=lcs_tab[i*(n+1)+j-1];
trace_tab[i*(n+1)+j]=LEFT;
}
}
}
printf("lcs_tab:\n");
for(i=0;i<=m;i++){
for(j=0;j<=n;j++)
printf("%-2d",lcs_tab[i*(n+1)+j]);
printf("\n");
}
printf("\ntarce_tab:\n");
for(i=0;i<=m;i++){
for(j=0;j<=n;j++){
switch(trace_tab[i*(n+1)+j]){
case TRACE_END:printf("● ");break;
case UP:printf("↑ ");break;
case LEFT:printf("← ");break;
case UP_AND_LEFT:printf("↖ ");break;
case UP_OR_LEFT:printf("←↑ ");break;
}
//printf("%-2d",trace_tab[i*(n+1)+j]);
}
printf("\n");
}
lcs_len=lcs_tab[m*(n+1)+n];
printf("\nlcs's len = %d\n",lcs_len);
int min_len=m>n?n:m;
sstrs=(char**)malloc(min_len*sizeof(char*));
for(i=0;i<min_len;i++)
*(sstrs+i)=(char*)malloc(MAX_LEN*sizeof(char));
sstrs_num=0;
if(lcs_len != 0){
out[lcs_len]='\0';
display_lcs(trace_tab,str_a,n,m,n,out,lcs_len-1,sstrs,&sstrs_num);
}
printf("in total %d kinds lcs!\n",sstrs_num);
return 0;
}
运行结果示意图为:
LCS问题就到这里啦~