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

本文出自点击打开链接  (稍做改动)


建立递归关系




计算最长公共子序列长度的动态规划算法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的情况开始返回,只有在搜索完所有的路径后算法才结束。


  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值