最长公共子序列

动态规划算法解LCS问题

1、最长公共子序列的结构

    最长公共子序列的结构有如下表示:

    设序列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的一个最长公共子序列及X和Yn-1的一个最长公共子序列。这两个公共子序列中较长者即为X和Y的一个最长公共子序列。

    由此递归结构容易看到最长公共子序列问题具有子问题重叠性质。例如,在计算X和Y的最长公共子序列时,可能要计算出X和Yn-1及Xm-1和Y的最长公共子序列。而这两个子问题都包含一个公共子问题,即计算Xm-1和Yn-1的最长公共子序列。

    与矩阵连乘积最优计算次序问题类似,我们来建立子问题的最优值的递归关系。用c[i,j]记录序列Xi和Yj的最长公共子序列的长度。其中Xi=<x1, x2, …, xi>,Yj=<y1, y2, …, yj>。当i=0或j=0时,空序列是Xi和Yj的最长公共子序列,故c[i,j]=0。其他情况下,由定理可建立递归关系如下:

3.计算最优值

    直接利用上节节末的递归式,我们将很容易就能写出一个计算c[i,j]的递归算法,但其计算时间是随输入长度指数增长的。由于在所考虑的子问题空间中,总共只有θ(m*n)个不同的子问题,因此,用动态规划算法自底向上地计算最优值能提高算法的效率。

    计算最长公共子序列长度的动态规划算法LCS_LENGTH(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]中。

Procedure LCS_LENGTH(X,Y);  
begin  
  m:=length[X];  
  n:=length[Y];  
  for i:=1 to m do c[i,0]:=0;  
  for j:=1 to n do c[0,j]:=0;  
  for i:=1 to m do  
    for j:=1 to n do  
      if x[i]=y[j] then  
        begin  
          c[i,j]:=c[i-1,j-1]+1;  
          b[i,j]:="↖";  
        end  
      else if c[i-1,j]≥c[i,j-1] then  
        begin  
          c[i,j]:=c[i-1,j];  
          b[i,j]:="↑";  
        end  
      else  
        begin  
          c[i,j]:=c[i,j-1];  
          b[i,j]:="←"  
        end;  
  return(c,b);  
end;   


    由于每个数组单元的计算耗费Ο(1)时间,算法LCS_LENGTH耗时Ο(mn)。

3、4.构造最长公共子序列

    由算法LCS_LENGTH计算得到的数组b可用于快速构造序列X=<x1, x2, …, xm>和Y=<y1, y2, …, yn>的最长公共子序列。首先从b[m,n]开始,沿着其中的箭头所指的方向在数组b中搜索。当b[i,j]中遇到"↖"时(意味着xi=yi是LCS的一个元素),表示Xi与Yj的最长公共子序列是由Xi-1与Yj-1的最长公共子序列在尾部加上xi得到的子序列;当b[i,j]中遇到"↑"时,表示Xi与Yj的最长公共子序列和Xi-1与Yj的最长公共子序列相同;当b[i,j]中遇到"←"时,表示Xi与Yj的最长公共子序列和Xi与Yj-1的最长公共子序列相同。这种方法是按照反序来找LCS的每一个元素的。

    下面的算法LCS(b,X,i,j)实现根据b的内容打印出Xi与Yj的最长公共子序列。通过算法的调用LCS(b,X,length[X],length[Y]),便可打印出序列X和Y的最长公共子序列。

Procedure LCS(b,X,i,j);  
begin  
  if i=0 or j=0 then return;  
  if b[i,j]="↖" then  
    begin  
      LCS(b,X,i-1,j-1);  
      print(x[i]); {打印x[i]}  
    end  
  else if b[i,j]="↑" then LCS(b,X,i-1,j)   
                      else LCS(b,X,i,j-1);  
end;   


在算法LCS中,每一次的递归调用使i或j减1,因此算法的计算时间为O(m+n)。

例如,设所给的两个序列为X=<A,B,C,B,D,A,B>和Y=<B,D,C,A,B,A>。由算法LCS_LENGTH和LCS计算出的结果如下图所示:

    我来说明下此图(参考算法导论)。在序列X={A,B,C,B,D,A,B}和 Y={B,D,C,A,B,A}上,由LCS_LENGTH计算出的表c和b。第i行和第j列中的方块包含了c[i,j]的值以及指向b[i,j]的箭头。在c[7,6]的项4,表的右下角为X和Y的一个LCS<B,C,B,A>的长度。对于i,j>0,项c[i,j]仅依赖于是否有xi=yi,及项c[i-1,j]和c[i,j-1]的值,这几个项都在c[i,j]之前计算。为了重构一个LCS的元素,从右下角开始跟踪b[i,j]的箭头即可,这条路径标示为阴影,这条路径上的每一个“↖”对应于一个使xi=yi为一个LCS的成员的项(高亮标示)。

    所以根据上述图所示的结果,程序将最终输出:“B C B A”。


给出代码:

#include <stdio.h>
#include <string.h>

int calLCS(char *cNumA,char *cNumB,int (*iLCS)[20],int iLenA,int iLenB)
{
	int i,j;

	for (i=0;i<=iLenA;i++)
	{
		iLCS[i][0]=0;
	}
	for (j=0;j<=iLenB;j++)
	{
		iLCS[0][j]=0;
	}

	for (i=1;i<=iLenA;i++)
	{
		for (j=1;j<=iLenB;j++)
		{
			if (cNumA[i-1]==cNumB[j-1])
			{
				iLCS[i][j]=iLCS[i-1][j-1]+1;
			}
			else 
			{
				if (iLCS[i-1][j]>=iLCS[i][j-1])
				{
					iLCS[i][j]=iLCS[i-1][j];
				}
				else
				{
					iLCS[i][j]=iLCS[i][j-1];
				}
			}
		}
	}
	return 0;
}

void printLCS(int (*iLCS)[20],char *cNumA,char *cNumB,int iLenA,int iLenB)
{
	int iIndex=0,i;
	char arrLCS[20];

	while (iLenA>0&&iLenB>0)
	{
		if (cNumA[iLenA-1]==cNumB[iLenB-1])
		{
			arrLCS[iIndex++]=cNumA[iLenA-1];
			iLenA--;
			iLenB--;
		}
		else
		{
			if (iLCS[iLenA-1][iLenB]>=iLCS[iLenA][iLenB-1])
			{
				iLenA--;
			}
			else
			{
				iLenB--;
			}
		}
	}
	
	printf("The LCS sequence is :  \n");
	for (i=iIndex-1;i>=0;i--)
	{
		printf("%c   ",arrLCS[i]);
	}
	printf("\n");
}

int main()
{
	char cNumA[20],cNumB[20];
	int iLCS[20][20],iLenA,iLenB;
	FILE *outfile=fopen("E://a.txt","r");
	if (!outfile)
	{
		printf("The file cannot be opened!\n");
		return 0;
	}

	printf("Input two strings!\n");
	fgets(cNumA,sizeof(cNumA),outfile);
	fgets(cNumB,sizeof(cNumB),outfile);

	iLenA=strlen(cNumA)-1;
	iLenB=strlen(cNumB)-1;

	calLCS(cNumA,cNumB,iLCS,iLenA,iLenB);
	printLCS(iLCS,cNumA,cNumB,iLenA,iLenB);

	return 0;
}


评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值