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的回溯构造最优解步骤例如以下图所看到的:



  • 相关阅读:
    hdoj2187:悼念512汶川大地震遇难同胞 (贪心)
    2.0其它之Transform详解,以及UIElement和FrameworkElement的常用属性
    2.0外观之样式, 模板, 视觉状态和视觉状态管理器
    2.0图形之Ellipse, Line, Path, Polygon, Polyline, Rectangle
    2.0控件之ListBox, MediaElement, MultiScaleImage, PasswordBox, ProgressBar, RadioButton
    2.0画笔之SolidColorBrush, ImageBrush, VideoBrush, LinearGradientBrush, RadialGradientBrush
    2.0图形之基类System.Windows.Shapes.Shape
    2.0交互之鼠标事件和键盘事件
    2.0控件之ScrollViewer, Slider, StackPanel, TabControl, TextBlock, TextBox, ToggleButton
    2.0交互之InkPresenter(涂鸦板)
  • 原文地址:https://www.cnblogs.com/zhchoutai/p/7371413.html
Copyright © 2011-2022 走看看