您的位置:首页 > 其它

动态规划 LCS 求两个序列A,B中全部的最长公共子序列

2017-08-08 21:04 246 查看
本文出自点击打开链接  (稍做改动)

建立递归关系



计算最长公共子序列长度的动态规划算法Lcs(X,Y)以序列X=<x1, x2, …, xm>和Y=<y1, y2, …, yn>作为输入。输出两个数组c[0..m ,0..n]和b[1..m ,1..n]。

其中c[i,j]存储Xi与Yj的最长公共子序列的长度,b[i,j]记录指示c[i,j]的值是由哪一个子问题的解达到的,这在构造最长公共子序列时要用到。最后,X和Y的

最长公共子序列的长度记录于c[m,n]中。

好了,直接求解所有LCS,其中求解所有LCS利用了回溯法,代码如下:

// 求两个序列A,B中所有的最长公共子序列
#include <stdio.h>
#include <string.h>
const int M = 100;

//记录序列X和Y的LCS的长度
int c[M][M];
//二维数组b记录搜索方向,1-对角线方向;2-向上;3-向左;4-向上或向左
int b[M][M];
//lcs 记录得到的LCS字符
char lcs[M];
// LCS最大长度
int  nlcs = 0;
/*
功能:
自底向上进行递推计算c[i][j],并确定b中的搜索方向
若i=0 或j=0 则c[i][j]=0
若i,j>0且x[i] == y[j]则c[i][j]==C[i-1][j-1]+1;
若i,j>0且x[i] !=y[j]则c[i][j]=max{C[i-1][j],C[i][j-1])
输入:两个序列x和y
输出:二维数组b和c
*/
int Lcs(char *x, char *y)
{
int lenX = strlen(x)-1;
int lenY = strlen(y)-1;
int i,j;

for(i=0; i<=lenX; i++)
{
memset(c[i], 0, sizeof(int)*lenY);
}

for(i=1; i<=lenX; i++)//0单元未用,下标从1开始
for(j=1; j<=lenY; 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 if(c[i-1][j] < c[i][j-1])
{
c[i][j] = c[i][j-1];
b[i][j] =3;//左
}
else //c[i-1][j] == c[i][j-1]
{
c[i][j] = c[i-1][j];
b[i][j] = 4;//左、上
}
}
return c[lenX][lenY];
}

/*
功能:回溯法递归输出所有的LCS
说明:
X:一个序列
curlen:记录当前LCS的长度
i,j:待搜索的下标,初始值为两个序列的长度
*/
void print_all(char *x,int curlen, int i, int j)
{
if(i<0 || j<0)
return ;
static int len =0;
//如果当前lcs的长度等于已知的LCS的长度则输出子序列
if(len == nlcs)
{
for(int k=nlcs-1; k>=0; k--)
printf("%c ", lcs[k]);
printf("\n");
}
else
{
if(b[i][j] ==1)
{
lcs[curlen] = x[i];
len++;//子序列长度加1
print_all(x,curlen+1,i-1,j-1);//沿对角线方向搜索
len--;//回溯
}
else if(b[i][j] ==2)
print_all(x,curlen,i-1, j);
else if(b[i][j] ==3)
print_all(x,curlen,i,j-1);
else//b[i][j] ==4 递归调用 沿上、左两个方向搜索
{
print_all(x,curlen,i,j-1);
print_all(x,curlen,i-1, j);
}
}
}

int main()
{
int i,j;
char x[100]={' '}, y[100]={' '};
while(1)
{
scanf("%s", x+1);//0单元未用,下标从1开始
scanf("%s", y+1);
int lx=strlen(x)-1, ly=strlen(y)-1;//0号下标未用
nlcs= Lcs(x,y);
printf("序列X和Y的所有子序列的LCS长度矩阵:\n");
for(i=0; i<=lx;i++)
{
for(j=0; j<=ly; j++)
printf("%d ", c[i][j]);
printf("\n");
}
printf("搜索方向标记矩阵,1-对角线方向;2-向上;3-向左;4-向上或向左:\n");
for(i=0; i<=lx;i++)
{
for(j=0; j<=ly; j++)
printf("%d ", b[i][j]);
printf("\n");
}
printf("最长公共子串长度为%d\n", nlcs);
printf("所有最长公共子串:\n");
print_all(x,0,lx,ly);
}
return 0;
}


输入:

ABCBDAB

BDCABA

运行结果:



在序列X={A,B,C,B,D,A,B}和 Y={B,D,C,A,B,A}上计算的表c (即第一个输出矩阵)和 算法导论上的相同,见下图:



从运行结果可以看出 输出了所有LCS, 主要用表b记录搜索方向,然后利用回溯法.

程序结果分析:

由print_all函数可知,求出所有的LCS就是根据数组b[i][j]中的值,即搜索的方向信息来遍历所有可能的路径找到满足条件的元素集合。

函数LCS的时间复杂度是求解数组c和b的执行时间,是O(mn+m+n)。而函数print_all的时间复杂度取决于遍历的路径数。在最好的情况下,

即X序列和Y序列完全相等,m=n,数组b中的值都是1(沿对角线方向),所以时间复杂度是O(m)。而在最坏情况下,即X序列和Y序列不

存在公共子序列,数组b中的值都是4,就是要分别沿向上和向左的方向搜索,这是每处理一次X[i]和Y[j],都必须沿着两个方向调用函数print_all,

当遇到i=0或j=0的情况开始返回,只有在搜索完所有的路径后算法才结束。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: