动态规划实例(二):最长公共子序列(LCS)

    本文参考http://www.cnblogs.com/hapjin/p/5572483.html

    给定两个字符串,求解这两个字符串的最长公共子序列(Longest Common Sequence)。比如字符串1:BDCABA;字符串2:ABCBDAB,则这两个字符串的最长公共子序列长度为4,最长公共子序列是:BCBA

二,算法求解
    这是一个动态规划的题目。对于可用动态规划求解的问题,一般有两个特征:①最优子结构;②重叠子问题
    ①最优子结构

    设 X=(x1,x2,.....xn) 和 Y={y1,y2,.....ym} 是两个序列,将 X 和 Y 的最长公共子序列记为LCS(X,Y),找出LCS(X,Y)就是一个最优化问题。因为,我们需要找到X 和 Y中最长的那个公共子序列。而要找X 和 Y的LCS,首先考虑X的最后一个元素和Y的最后一个元素。

    1)如果 xn=ym,即X的最后一个元素与Y的最后一个元素相同,这说明该元素一定位于公共子序列中。因此,现在只需要找:LCS(Xn-1,Ym-1),LCS(Xn-1,Ym-1)就是原问题的一个子问题。为什么叫子问题?因为它的规模比原问题小。(小一个元素也是小嘛....)为什么是最优的子问题?因为我们要找的是Xn-1 和 Ym-1 的最长公共子序列啊。。。最长的!!!换句话说,就是最优的那个。(这里的最优就是最长的意思)
    2)如果xn != ym,这下要麻烦一点,因为它产生了两个子问题:LCS(Xn-1,Ym) 和 LCS(Xn,Ym-1)因为序列X 和 序列Y 的最后一个元素不相等嘛,那说明最后一个元素不可能是最长公共子序列中的元素嘛。(都不相等了,怎么公共嘛)。
        LCS(Xn-1,Ym)表示:最长公共序列可以在(x1,x2,....x(n-1)) 和 (y1,y2,...yn)中找。
        LCS(Xn,Ym-1)表示:最长公共序列可以在(x1,x2,....xn) 和 (y1,y2,...y(n-1))中找。
    求解上面两个子问题,得到的公共子序列谁最长,那谁就是 LCS(X,Y)。用数学表示就是:
        LCS=max{LCS(Xn-1,Ym),LCS(Xn,Ym-1)}
    由于条件 1)  和  2)  考虑到了所有可能的情况。因此,我们成功地把原问题 转化 成了 三个规模更小的子问题。
   ②重叠子问题

    重叠子问题是啥?就是说原问题 转化 成子问题后,  子问题中有相同的问题。咦?我怎么没有发现上面的三个子问题中有相同的啊????

    OK,来看看,原问题是:LCS(X,Y)。子问题有 ❶LCS(Xn-1,Ym-1)    ❷LCS(Xn-1,Ym)    ❸LCS(Xn,Ym-1)初一看,这三个子问题是不重叠的。可本质上它们是重叠的,因为它们只重叠了一大部分。举例:第二个子问题:LCS(Xn-1,Ym) 就包含了:问题❶LCS(Xn-1,Ym-1),为什么?
    因为,当Xn-1 和 Ym 的最后一个元素不相同时,我们又需要将LCS(Xn-1,Ym)进行分解:分解成:LCS(Xn-1,Ym-1) 和 LCS(Xn-2,Ym)。也就是说:在子问题的继续分解中,有些问题是重叠的。
    由于像LCS这样的问题,它具有重叠子问题的性质,因此:用递归来求解就太不划算了。因为采用递归,它重复地求解了子问题啊。而且注意哦,所有子问题加起来的个数 可是指数级的哦。。。。
    这篇文章中就演示了一个递归求解重叠子问题的示例。
    那么问题来了,你说用递归求解,有指数级个子问题,故时间复杂度是指数级。这指数级个子问题,难道用了动态规划,就变成多项式时间了??

    关键是采用动态规划时,并不需要去一 一 计算那些重叠了的子问题。或者说:用了动态规划之后,有些子问题 是通过 “查表“ 直接得到的,而不是重新又计算一遍得到的。废话少说:举个例子吧!比如求Fib数列。

    LCS具体实例及实现代码如下所示:

/**
 * @Title: LCS.java
 * @Package dynamicprogramming
 * @Description: TODO
 * @author peidong
 * @date 2017-6-5 上午9:34:15
 * @version V1.0
 */
package dynamicprogramming;

import java.util.Stack;

/**
 * @ClassName: LCS
 * @Description: 最长公共子序列
 * @date 2017-6-5 上午9:34:15
 *
 */

public class LCS {
    /**
     *
     * @Title: lcs1
     * @Description: 递归的方式获取最长公共子序列
     * @param x  字符串数组
     * @param y  字符串数组
     * @param m  数组长度
     * @param n  数组长度
     * @return
     * @return int
     * @throws
     */
    public static int lcs1(char[] x, char[] y, int m, int n){
        //边界条件判断
        if(m == 0 || n ==0)
            return 0;
        if(x[m-1] == y[n-1])
            return 1 + lcs1(x, y, m-1, n-1);
        else
            return max(lcs1(x, y, m-1, n), lcs1(x, y, m, n-1));
    }

    /**
     *
     * @Title: max
     * @Description: 返回较大值
     * @param a
     * @param b
     * @return
     * @return int
     * @throws
     */
    public static int max(int a, int b){
        return (a > b) ? a : b;
    }

    /**
     *
     * @Title: lcs2
     * @Description: 动态规划求解最长公共子串
     * @param x   字符串数组
     * @param y   字符串数组
     * @param m   数组长度
     * @param n   数组长度
     * @return int
     * @throws
     */
    public static int lcs2(char[] x, char[] y, int m, int n){
        //初始化数组
        int[][] L = new int[m+1][n+1];

        //求解过程
        for(int i = 0; i <= m; i++){
            for(int j = 0; j <= n; j++){
                if( i == 0 || j == 0){
                    L[i][j] = 0;
                }else if( x[i - 1] == y[j - 1]){
                    L[i][j] = L[i - 1][j - 1] + 1;
                }else{
                    L[i][j] = max(L[i][j - 1], L[i - 1][j]);
                }
            }
        }
        Stack<Character> stack = new Stack();
        int m1 = x.length - 1;
        int n1 = y.length - 1;
        while (n1 >= 0 && m1 >= 0) { // 遍历数组
            if (x[m1] == y[n1]) {
                stack.push(x[m1]);
                m1--;
                n1--;
            } else {// 字符不同时,根据打印出的二维矩阵(测试数据)查找上一个相同的字符
                if (L[m1 + 1][n1] > L[m1][n1 + 1]) {
                    n1--;
                } else {
                    m1--;
                }
            }
        }
        System.out.println("最长公共子序列为:");
        while (!stack.isEmpty()) {// 打印出最长的递增子序列
            System.out.print(stack.pop() + ",");
        }
        return L[m][n];
    }

    /**
     * @Title: main
     * @Description: TODO
     * @param args
     * @return void
     * @throws
     */
    public static void main(String[] args) {
        // TODO Auto-generated method stub

        char[] x ={'A', 'G', 'G', 'T', 'A', 'B'};
        char[] y = {'G', 'X', 'T', 'X', 'A', 'Y', 'B'};
        int m = x.length;
        int n = y.length;

        int res1 = lcs1(x, y, m, n);
        int res2 = lcs2(x, y, m, n);
        System.out.println();
        System.out.println("使用递归获取最长公共子序列长度为:" + res1);
        System.out.println("使用dp获取最长公共子序列长度为:" + res2);
    }

}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值