动态规划之最长公共子序列

问题描述:

序列Z=(B,C,D,B),序列X=(A,B,C,B,D,A,B),序列Z是序列X的子序列(subsequence)。因为Z中的元素在X中满足严格递增的下标(2,3,5,7),设第一个字符‘A’下标为1。

给定两个序列X,Y,如果序列Z即是X的子序列又是Y的子序列,则称Z是X,Y的公共子序列(common subsequence)。

(注意:如果求连续的序列,叫子串;不连续的序列,叫子序列。)


如果使用普通的字符串比较方法,穷举X的所有子序列,判断是否也为Y的子序列,如果是则记录下长度,最后通过比较得到最长的长度。

但是,这种方法太麻烦。例:一个含有m个元素的序列,它含有2^m个子序列。穷举法求解的时间复杂度在指数级别


动态规划法:

最优子结构性质:

对序列X={x1,x2.....xm},Y={y1,y2......yn},Z={z1,z2,......zk}是X,Y的最长公共子序列。则满足一下关系式:

1.如果xm==yn,则zk==xm==yn,且X={x1,x2.....x(m-1)},Y={y1,y2......y(n-1)}的最长公共子序列为Z={z1,z2,......z(k-1)};

2.如果xm!=yn,且zk!=xm,则Z是X(m-1)和Y的最长公共子序列;

3.如果xm!=yn,且zk!=yn,则Z是Y(m-1)和X的最长公共子序列.

因为X,Y得最长公共子序列包含了两个序列前缀的最长公共子序列,表示最长公共子序列具有最优子结构特性。


最优解的递推关系:

1.如果xm==yn,则求出X(m-1)与Y(n-1)的最长公共子序列,然后+1,得到Xm与Yn的最长公共子序列;

2.如果xm!=yn,则分别求X(m-1)与Y,X与Y(n-1)的最长公共子序列,两个公共子序列中较长者就是X,Y的最长公共子序列。

用矩阵来表示这种关系较为方便。

矩阵满足一下递推式:

               0                 i=0,j=0

c[i][j]=    c[i-1][j-1]+1   i,j>0&&xi=yj

               max{c[i-1][j],c[i][j-1]}   i,j>0&&xi!=yj

代码实现

//s1,s2为两个序列,a,b为两个二位数组,存储长度与关系
 char s1[1000],s2[1000],s3[1000],s4[1000];int **a,int **b;
 void init()          //初始化数据
{
       s1[0]='0';    //至于为什么要在序列s1前加一个‘0’字符,看下面的例子。
       s2[0]='0'
       cin>>s3;
       cin>>s4;
       strcat(s1,s3);
       strcat(s2,s4);
       int l1=strlen(s1);
       int l2=strlen(s2);
       a=new int*[l1];
       b=new int*[l1];
       for(int i=0;i<l1;i++)
       {
               a[i]=new int[l2];
               for(int j=0;j<l2;j++)
               {
                      a[i][0]=a[0][j]=0;
                      b[i][0]=b[0][j]=0;
                }
}
计算最长公共序列:

int LCSLength(char *s1,char *s2,int **a,int **b,int l1,int l2)
{
        for(int i=1;i<l1;i++)
       {
            for(int j=1;j<l2;j++)
            {
                 if(s1[i]==s2[j])
                 {
                     c[i][j]=c[i-1][j-1]+1;
                      b[i][j]=1;
                  }
                 else
                      if(c[i-1][j]>=c[i][j-1])
                      {
                            c[i][j]=c[i-1][j];
                            b[i][j]=2;
                        }
                       else
                       {
                              c[i][j]=c[i][j-1];
                             b[i][j]=3;
                        }
               }
         }
         return a[l1-1][l2-1];            //最优解
}
如果想输出这个序列,调用一下函数

void CL(int i,int j)
{
     if(i==0||j==0)       return;
     if(b[i][j]==1)
     {
            CL(i-1,j-1);
            cout<<s1[i];
        }
      if(b[i][j]==2)
      {
            CL(i-1,j);
       }
       if(b[i][j]==3)
       {
             CL(i,j-1);
        }
}


例子:

s1="0abcbdab"    s2="0bdcaaba"

                  二维数组a,b

 01(b)2(d)3(c)4(a)5(b)6(a)
00000000
1(a)0000111
2(b)0111122
3(c)0112222
4(b)0112233
5(d)0122233
6(a)0122334
7(b)0122344

 01(b)2(d)3(c)4(a)5(b)6(a)
00000000
1(a)0222131
2(b)0133213
3(c)0221322
4(b)0122213
5(d)0212222
6(a)0222121
7(b)0122212


可用测算法测试 POJ 1458  http://poj.org/problem?id=1458

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值