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

动态规划法

  动态规划法(dynamic programming)通常用于求解最优化问题(optimization problem),它适用于最优子结构和重叠子问题。这显然与分治法是不同的,分治法将问题划分为不重叠的子问题,然后分别求解这些子问题,最后将这些问题合并得到最终的解。

     对于具有公共子问题的情况,分治法会做很多不必要的工作,它会多次求解同一子子问题。动态规划法却不一样,对每个子子问题它只会求解一次,将其保存在一个表格中,避免了不必要的重复计算。

     如之前所说,动态规划法用于求解最优化问题,这就意味着可能这个问题,有很多解,但是呢,不一定都是最优解。利用动态规划法求出来的是这个问题的一个最优解(an optimal solution),记住这里求解的只是最优解(the optimal solution)中的一个,因为最优解可能有多个。

设计一个问题的动态规划算法主要有一下的几步

    (1)       找出最优解的性质,刻画其结构特征;

    (2)       递归的定义最优解的值;

    (3)       以自底向上的方式计算出最优值;

    (4)       根据计算最优解时得到的信息,构造一个最优解。


     最长公共子序列的动态规划法实现

最长公共子序列(longest-common-subsequence, LCS)
     (1)子序列:一个序列X = x1x2...xn,中任意删除若干项,剩余的序列叫做A的一个子序列。也可以认为是从序列A按原顺序保留任意若干项得到的序列。
      例如:对序列 1,3,5,4,2,6,8,7来说,序列3,4,8,7 是它的一个子序列。对于一个长度为n的序列,它一共有2^n 个子序列,有(2^n – 1)个非空子序列。在这里需要提醒大家,子序列不是子集,它和原始序列的元素顺序是相关的。

    (2)公共子序列:如果序列Z既是序列X的子序列,同时也是序列Y的子序列,则称它为序列X和序列Y的公共子序列。空序列是任何两个序列的公共子序列。

     (3)最长公共子序列:X和Y的公共子序列中长度最长的(包含元素最多的)叫做X和Y的最长公共子序列。

      这个问题如果用穷举法时间,最终求出最长公共子序列时,时间复杂度是Ο(2mn),是指数级别的复杂度,对于长序列是不适用的。因此我们使用动态规划法来求解。
 

第一步:刻画最长公共子序列问题的最优子结构
      设X=x1x2…xm和Y=y1y2…yn是两个序列,Z=z1z2…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=yn,那么我们应该求解Xm-1,Yn-1的一个LCS,并且将xm=yn加入到这个LCS的末尾,这样得到的一个新的LCS就是所求。

      如果xm≠yn,我们需要求解两个子问题,分别求Xm-1,Y的一个LCS和X,Yn-1的一个LCS。两个LCS中较长者就是X和Y的一个LCS。

      可以看出LCS问题具有重叠子问题性质。为了求X和Y的一个LCS,我们需要分别求出Xm-1,Y的一个LCS和X,Yn-1的一个LCS,这几个字问题又包含了求出Xm-1,Yn-1的一个LCS的子子问题。(有点绕了。。。晕没晕。。。。)

       根据上面的分析,我们可以得出下面的公式:


计算最优值


public class Main {

    public static void main(String[] args) {
        char[] x = {'A', 'B', 'C', 'B', 'D', 'A', 'B'};
        char[] y = {'B', 'D', 'C', 'A', 'B', 'A'};
        int t=LCCSLength(x, y);
        System.out.println(t);
    }

    public static int LCCSLength(char[] x, char[] y) {
        int[][] dp =new int[x.length+1][y.length+1];//动态规划表,记录每一步最优值
        for(int i=0;i<=x.length;i++){
            dp[i][0]=0;
        }
        for(int j=1;j<=y.length;j++){
            dp[0][j]=0;
        }
/*
           如果是  for(int i=1;i<x.length;i++){
                        for(int j=1;j<y.length;j++){

                        }
                  }
            那么
                 dp[i][j]=dp[i-1][j-1]+1
*/
        for(int i=0;i<x.length;i++){
            for(int j=0;j<y.length;j++){
//                if(x[i]==y[j]){
//                    dp[i+1][j+1]=dp[i][j]+1;
//                }else if(dp[i+1][j]>=dp[i][j+1]){
//                    dp[i+1][j+1]=dp[i+1][j];
//                }else {
//                    dp[i+1][j+1]=dp[i][j+1];
//                }

                if(x[i]==y[j]){
                    dp[i+1][j+1]=dp[i][j]+1;
                }else{
                    dp[i+1][j+1]=(dp[i+1][j]>=dp[i][j+1]?dp[i+1][j]:dp[i][j+1]);
                }
            }
        }
        for(int i=0;i<=7;i++){
            for(int j=0;j<=6;j++){
                System.out.print(dp[i][j]);
            }
            System.out.println();
        }
        return dp[x.length][y.length];
    }
}




构造最优解

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值