动态规划解决最长公共子序列LCS问题

子序列的定义:给定一个序列X=<x1,x2,...,xm>,另一个序列Z=<z1,z2,...,zk>,满足如下条件时称为X的子序列,

即存在一个严格递增的X的下标序列<i1,i2,...,ik>和所有的j=1,2,...,k,满足Zj=X[ik]下标序列,使得它们都相等。

如<B,C,D,B>是X=<A,B,C,B,D,A,B>的子序列。

给定两个序列X和Y,如果Z既是X的子序列,也是Y的子序列,我们称它是X和Y的公共子序列。

LCS问题:给定两个序列,求X和Y长度最长的公共子序列。

分析:LCS具有最优子结构性质,两个序列的LCS包含两个序列的前缀的LCS。

定义C[0,...,m][0,....,n],m是X序列的长度,Y序列的长度是n。C[i][j]的含义是序列X[0,...,i-1]和序列Y[0,...,j-1]的LCS。

如果X[i-1]==Y[j-1],则c[i][j]=c[i-1][j-1]+1;如果X[i-1]!=Y[j-1],则序列X[0,...,i-1]和序列Y[0,...,j-1]的LCS有两种可能,

一种是序列X[0,...,i-2]和序列Y[0,...,j-1]的LCS,另一种是序列X[0,...,i-1]和序列Y[0,...,j-2]的LCS,两者取较大值。

即c[i][j]=max(c[i][j-1],c[i-1][j])。

下面是实现的过程:

// LCS.cpp : 定义控制台应用程序的入口点。
//最长公共子序列问题:给定两个序列X=
   
   
    
    和Y=
    
    
     
     ,求X和Y的最长公共子序列。

#include "stdafx.h"
#include 
     
     
      
      
#include 
      
      
       
       
#include 
       
       
         #include 
        
          using namespace std; //c[i][j]表示X的前i个字符和Y的前j个字符所共有的序列长度;b[i][j]用来回溯得到的公共子序列 void LCS_LENGTH(string X,string Y,vector 
          
          
            > & b,vector 
            
            
              > & c) { int m=X.length(),n=Y.length(); for(int i=0;i<=m;i++) c[i][0]=0; for(int j=0;j<=n;j++) c[0][j]=0; int i,j; for(i=1;i<=m;i++) for(j=1;j<=n;j++) if(X[i-1]==Y[j-1]) { c[i][j]=c[i-1][j-1]+1; b[i][j]=2; }else if(c[i-1][j]>=c[i][j-1]) { c[i][j]=c[i-1][j]; b[i][j]=1; }else { c[i][j]=c[i][j-1]; b[i][j]=0; } } //利用回溯和表b,追踪LCS void PRINT_LCS(vector 
              
              
                > & b, string X,int xlen, int ylen,string &s) { int i=xlen,j=ylen; if(i==0||j==0) return ; if(b[i][j]==2)//此时第i个元素X[i-1][j-1]属于公共元素 { PRINT_LCS(b,X,i-1,j-1,s); s+=X[i-1]; }else if(b[i][j]==1)//上面有c[i][j]=c[i-1][j];此时回溯c[i-1][j] PRINT_LCS(b,X,i-1,j,s); else PRINT_LCS(b,X,i,j-1,s); } int _tmain(int argc, _TCHAR* argv[]) { string X="ABCBDAB",Y="BDCABA"; int m=X.length(),n=Y.length(); vector 
                
                
                  > b(m+1), c(m+1); string s; for(int i=0;i<=m;i++) { b[i].resize(n+1); c[i].resize(n+1); } LCS_LENGTH(X,Y,b,c); PRINT_LCS(b,X,m,n,s); cout<<"X:"< 
                 
                   < 
                   
                  
                 
                
               
              
             
            
           
          
         
       
      
      
     
     
    
    
   
   

它的时间复杂度是: O(m*n)   空间复杂度:O(m*n)

进一步优化:

动态规划解决LCS,一般要定义一个表c[0,...,m][0,...,n],所用的空间复杂的是O(m*n),其实这里表c的信息是冗余的。要想获得c[i][j]其实,只需根据c[i-1][j-1],c[i][j-1],c[i-1][j]中的信息获得即可。我们可以用O(m)或者O(n)的空间

就可以获得最长公共子序列的长度。我们这里定义一个数组a[m+1],存储如图中绿色部分的信息,a[0]=c[i-1][j-1],

当进行到比较X[i]与Y[j]时,我们要得到c[i,j]的值,它需要用到c[i-1][j-1],c[i][j-1],c[i-1][j],即a[0],a[i],a[i-1]。同时,它是新的a[i],如第二张图的红色部分所示。同时,原来a[0]里存储的数据用过后,再下一次循环就用不上了,a[0]也需要更新。然后,这次循环结束后,就形成了一个新的a数组。我们可以看到,它是从上到下,一列列的生成c[i][j]中的元素的。注意,当生成了一列结束,即j>=m时,开始新的外一层循环,此时,设置a[0]=0.



下面的代码在原有的基础增加了一个函数LCS(....),用O(m+1)的空间获得LCS长度,这里没有恢复重构LCS中的元素。

// LCS.cpp : 定义控制台应用程序的入口点。
//最长公共子序列问题:给定两个序列X=
    
    
     
     和Y=
     
     
      
      ,求X和Y的最长公共子序列。

#include "stdafx.h"
#include 
      
      
       
       
#include 
       
       
        
        
#include 
        
        
          #include 
         
           using namespace std; //这里对算法进行了优化,空间复杂度O(m+1) void LCS(string X, string Y, vector 
          
            & a) { int m=X.length(),n=Y.length(); for (int j = 1; j <= n; j++) { a[0]=0;//内层循环结束,a[0]要置零,想象求a[1][j]所依赖的a[0][j-1]=0,即是初始化步骤 for (int i = 1; i <= m; i++) { int pre=a[0]; int temp = a[i]; if (X[i - 1] == Y[j - 1]) a[i] =pre+1; else a[i] = max(a[i],a[i-1]); a[0]=temp; } } } //c[i][j]表示X的前i个字符和Y的前j个字符所共有的序列长度;b[i][j]用来回溯得到的公共子序列 void LCS_LENGTH(string X,string Y,vector 
            
            
              > & b,vector 
              
              
                > & c) { int m=X.length(),n=Y.length(); for(int i=0;i<=m;i++) c[i][0]=0; for(int j=0;j<=n;j++) c[0][j]=0; int i,j; for(i=1;i<=m;i++) for(j=1;j<=n;j++) if(X[i-1]==Y[j-1]) { c[i][j]=c[i-1][j-1]+1; b[i][j]=2; }else if(c[i-1][j]>=c[i][j-1]) { c[i][j]=c[i-1][j]; b[i][j]=1; }else { c[i][j]=c[i][j-1]; b[i][j]=0; } } //利用回溯和表b,追踪LCS void PRINT_LCS(vector 
                
                
                  > & b, string X,int xlen, int ylen,string &s) { int i=xlen,j=ylen; if(i==0||j==0) return ; if(b[i][j]==2)//此时第i个元素X[i-1][j-1]属于公共元素 { PRINT_LCS(b,X,i-1,j-1,s); s+=X[i-1]; }else if(b[i][j]==1)//上面有c[i][j]=c[i-1][j];此时回溯c[i-1][j] PRINT_LCS(b,X,i-1,j,s); else PRINT_LCS(b,X,i,j-1,s); } int _tmain(int argc, _TCHAR* argv[]) { string X="ABCBDABEF",Y="BDCABAEF"; int m=X.length(),n=Y.length(); vector 
                  
                  
                    > b(m+1), c(m+1); string s; for(int i=0;i<=m;i++) { b[i].resize(n+1); c[i].resize(n+1); } LCS_LENGTH(X,Y,b,c); PRINT_LCS(b,X,m,n,s); cout<<"X:"< 
                   
                     < 
                    
                      a(m+1); LCS(X,Y ,a); cout< 
                     
                       < 
                       
                      
                     
                    
                   
                  
                 
                
               
              
             
            
           
          
        
       
       
      
      
     
     
    
    


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值