最长公共子序列问题LCS

Posted jhcelue

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了最长公共子序列问题LCS相关的知识,希望对你有一定的参考价值。

最长公共子序列问题LCS

  问题描写叙述

一个给定序列的子序列是在该序列中删去若干元素后得到的序列。确切地说。若给定序列X= { x1, x2,…, xm},则还有一序列Z= {z1, z2,…, zk}是X的子序列是指存在一个严格递增的下标序列 {i1, i2,…, ik},使得对于全部j=1,2,…,k有 Xij=Zj。比如,序列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= { A, B, C, B, D, A, B}和Y= {B, D, C, A, B, A},则序列{B,C,A}是X和Y的一个公共子序列,序列{B,C,B,A}也是X和Y的一个公共子序列。并且,后者是X和Y的一个最长公共子序列。由于X和Y没有长度大于4的公共子序列。给定两个序列X= {x1, x2, …, xm}和Y= {y1, y2, … , yn},要求找出X和Y的一个最长公共子序列。

     问题解析

设X= { A, B, C, B, D, A, B},Y= {B, D, C, A, B, A}。求X,Y的最长公共子序列最easy想到的方法是穷举法。

对X的多有子序列,检查它是否也是Y的子序列。从而确定它是否为X和Y的公共子序列。由集合的性质知,元素为m的集合共同拥有2^m个不同子序列,因此,穷举法须要指数级别的运算时间。进一步分解问题特性。最长公共子序列问题实际上具有最优子结构性质。

      设序列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}。

  • xm=yn(最后一个字符同样)。则不难用反证法证明:该字符必是X与Y的任一最长公共子序列Z(设长度为k)的最后一个字符,即有zk = xm = yn 且显然有Zk-1∈LCS(Xm-1 , Yn-1)即Z的前缀Zk-1是Xm-1与Yn-1的最长公共子序列。此时。问题化归成求Xm-1与Yn-1的LCS(LCS(X , Y)的长度等于LCS(Xm-1 , Yn-1)的长度加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-1与Y的LCS及X与Yn-1的LCS。

    LCS(X , Y)的长度为:max{LCS(Xm-1 , Y)的长度, LCS(X , 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;当i,j>0,xi=yj时,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]},由此建立递推关系例如以下:

技术分享

          构造最优解

由以上分析可知,要找出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的最长公共子序列。设数组record[i][j]记录c[i][j]的值由哪一个子问题的解得到的。从record[m][n]開始。依其值在数组b中搜索,当record[i][j]=1时,表示Xi和Yj的最长公共子序列是由Xi-1和Yj-1的最长公共子序列在尾部加上xi所得到的子序列。当record[i][j]=2时,表示Xi和Yj的最长公共子序列与Xi-1和Yj-1的最长公共子序列同样。

当record[i][j]=3时。表示Xi和Yj的最长公共子序列与Xi和Yj-1的最长公共子序列同样。

代码:
#include<iostream>
#include<cstdio>
#include<string>
#include<algorithm>
#define MAX 1000

using namespace std;

int dp[MAX][MAX];
int record[MAX][MAX];

int LCSLength(string strA,string strB)
{
	int i,j;
	int lenA=strA.length();
	int lenB=strB.length();
	for(i=0;i<lenA;i++)
		dp[i][0]=0;
	for(j=0;j<lenB;j++)
		dp[0][j]=0;
	for(i=0;i<lenA;i++)
	{
		for(j=0;j<lenB;j++)
		{
			if(i==0||j==0)
			{
				if(strA[i]==strB[j])
				{
					dp[i][j]=1;
					record[i][j]=1;
				}
				else
				{
					if(i>0)
					{
						dp[i][j]=dp[i-1][j];
						record[i][j]=3;
					}
					if(j>0)
					{
						dp[i][j]=dp[i][j-1];
						record[i][j]=2;
					}
				}
			}
			else if(strA[i]==strB[j])
			{
				dp[i][j]=dp[i-1][j-1]+1;
				record[i][j]=1;
			}
			else if(dp[i-1][j]>=dp[i][j-1])
			{
				dp[i][j]=dp[i-1][j];
				record[i][j]=3;
			}
			else
			{
				dp[i][j]=dp[i][j-1];
				record[i][j]=2;
			}
		}
	}
	return dp[lenA-1][lenB-1];
}

void LCS(int i,int j,string strA)
{
	if(i<0||j<0)
		return ;
	if(record[i][j]==1)
	{
		LCS(i-1,j-1,strA);
		cout<<strA[i];
	}
	else if(record[i][j]==2)
		LCS(i,j-1,strA);
	else
		LCS(i-1,j,strA);
}

int main(int argc,char *argv[])
{
	string strA,strB;
	cin>>strA>>strB;
	cout<<"LCSLength: "<<LCSLength(strA,strB)<<endl;
	cout<<"LCS: ";
	LCS(strA.length()-1,strB.length()-1,strA);
	cout<<endl;
	cout<<"Record:\n";
	for(int i=0;i<strA.length();i++)
	{
		for(int j=0;j<strB.length();j++)
			printf("%3d",record[i][j]);
		printf("\n");
	}
	return 0;
}
在这里处理边界条件(i=0||j=0)分类比較麻烦。事实上还能够把它们合并为一条语句;
代码:
<span style="font-size:18px;">#inc</span><span style="font-size: 18px;">lude<cstdio>
#include<iostream>
#include<string>
#include<algorithm>
#define MAX 1000

using namespace std;

int dp[MAX][MAX];
int record[MAX][MAX];

int LCSLength(string strA,string strB)
{
	int i,j;
	int lenA=strA.length();
	int lenB=strB.length();
	for(i=0;i<=lenA;i++)
		dp[i][0]=0;
	for(j=0;j<=lenB;j++)
		dp[0][j]=0;
	for(i=1;i<=lenA;i++)
	{
		for(j=1;j<=lenB;j++)
		{
			if(strA[i-1]==strB[j-1])//注意下标从0開始,所以这里有些许不一样
			{                   //dp[i][j]存储的是Xi-1与Yj-1的最长LCS
				dp[i][j]=dp[i-1][j-1]+1;
				record[i][j]=1;
			}
			else if(dp[i-1][j]>=dp[i][j-1])
			{
				dp[i][j]=dp[i-1][j];
				record[i][j]=3;
			}
			else
			{
				dp[i][j]=dp[i][j-1];
				record[i][j]=2;
			}
		}
	}
	return dp[lenA][lenB];
}

void LCS(int i,int j,string strA)
{
	if(i==0||j==0)
		return ;
	if(record[i][j]==1)
	{
		LCS(i-1,j-1,strA);
		cout<<strA[i-1];
	}
	else if(record[i][j]==2)
		LCS(i,j-1,strA);
	else
		LCS(i-1,j,strA);
}

int main(int argc,char *argv[])
{
	string strA,strB;
	cin>>strA>>strB;
	cout<<"LCSLength: "<<LCSLength(strA,strB)<<endl;
	cout<<"LCS: ";
	LCS(strA.length(),strB.length(),strA);
	cout<<endl;
	cout<<"Record:\n";
	for(int i=0;i<=strA.length();i++)
	{
		for(int j=0;j<=strB.length();j++)
			printf("%3d",record[i][j]);
		printf("\n");
	}
	return 0;
}
</span>

LCSLength函数在计算最优值时,分别迭代X。Y构造数组b,c。设数组每一个元素单元计算耗费时间O(1),则易得算法LCSLength的时间复杂度为O(mn)。在算法LCS中,根据数组b的值回溯构造最优解,每一次递归调用使i,或j减小1。从而算法的计算时间为O(m+n)。

LCS的回溯构造最优解步骤例如以下图所看到的:

技术分享





以上是关于最长公共子序列问题LCS的主要内容,如果未能解决你的问题,请参考以下文章

最长公共子序列(LCS),求LCS长度和打印输出LCS

每日一题-——最长公共子序列(LCS)与最长公共子串

最长公共子序列(LCS)和逆LCS问题求解

最长公共子序列(LCS)

最长公共子串和最长公共子序列(LCS问题)

最长公共子串和最长公共子序列(LCS问题)