• 最长公共子序列问题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的回溯构造最优解步骤例如以下图所看到的:



  • 相关阅读:
    springcloud-spring cloud config统一配置中心
    springcloud-hystrix断路器对微服务的容错处理
    springcloud-feign组件实现声明式的调用
    springcloud-Ribbon-负载均衡组件
    springcloud-Eureka-服务注册与发现核心组件
    springcloud入门-什么是springcloud
    Redis缓存设计及常见问题
    Lucene全文检索入门使用
    redis安装、使用
    nodejs环境 + 入门 + 博客搭建
  • 原文地址:https://www.cnblogs.com/zhchoutai/p/7371413.html
Copyright © 2020-2023  润新知