zoukankan      html  css  js  c++  java
  • 最长公共子序列问题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:
    ";
    	for(int i=0;i<strA.length();i++)
    	{
    		for(int j=0;j<strB.length();j++)
    			printf("%3d",record[i][j]);
    		printf("
    ");
    	}
    	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:
    ";
    	for(int i=0;i<=strA.length();i++)
    	{
    		for(int j=0;j<=strB.length();j++)
    			printf("%3d",record[i][j]);
    		printf("
    ");
    	}
    	return 0;
    }
    </span>

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

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



  • 相关阅读:
    error: expected unqualified-id extern "C" {
    cmake交叉编译android(转)
    wwindows文件放入linux后多出换行符
    JNI 引用问题梳理(转)
    OpenCV实现任意大小图片的合并(转)
    android camera preview常用格式
    Linux下找不到动态链接库(转)
    ld链接器的工作原理及链接顺序(转)
    添加静态库入动态库时,符号查找不到的问题
    如何捕获 System.loadLibrary 产生的异常?(转)
  • 原文地址:https://www.cnblogs.com/jhcelue/p/6971534.html
Copyright © 2011-2022 走看看