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

 附上转载地址:
http://blog.csdn.net/lisonglisonglisong/article/details/41548557

                                                          

下面通过一个具体的例子来学习动态规划方法 —— 最长公共子序列问题。

最长公共子串(Longest Common Substring)最长公共子序列(Longest Common Subsequence)的区别: 子串要求在原字符串中是连续的,而子序列则只需保持相对顺序,并不要求连续。

问题描述:给定两个序列:X[1...m]Y[1...n],求在两个序列中同时出现的最长子序列的长度。

假设 X 和 Y 的序列如下:

[plain] view plain copy 在CODE上查看代码片派生到我的代码片

    X[1...m] = {A, B, C, B, D, A, B}  
    Y[1...n] = {B, D, C, A, B, A}  


可以看出,X 和 Y 的最长公共子序列有 “BDAB”、“BCAB”、“BCBA”,即长度为4。

1) 穷举法

可能很多人会想到用穷举法来解决这个问题,即求出 X 中所有子序列,看 Y 中是否存在该子序列。

  • X 有多少子序列 —— 2m 个
  • 检查一个子序列是否在 Y 中 —— θ(n)

所以穷举法在最坏情况下的时间复杂度是 θ(n * 2m),也就是说花费的时间是指数级的,这简直太慢了。

2) 动态规划

首先,我们来看看 LCS 问题是否具有动态规划问题的两个特性。

① 最优子结构

设 C[i,j] = |LCS(x[1...i],y[1...j])|,即C[i,j]表示序列X[1...i]Y[1...j]的最长公共子序列的长度,则 C[m,n] = |LCS(x,y)|就是问题的解。

递归推导式:


在这里就不证明了。从这个递归公式可以看出,问题具有最优子结构性质!

② 重叠子问题

根据上面的递归推导式,可以写出求LCS长度的递归伪代码:

[cpp] view plain copy 在CODE上查看代码片派生到我的代码片

    LCS(x,y,i,j)  
        if x[i] = y[j]  
            then C[i,j] ← LCS(x,y,i-1,j-1)+1  
            else C[i,j] ← max{LCS(x,y,i-1,j),LCS(x,y,i,j-1)}  
        return C[i,j]  


C++代码如下:

[cpp] view plain copy 在CODE上查看代码片派生到我的代码片

    // 简单的递归求解LCS问题  
    #include <iostream>  
    #include <string>  
    using namespace std;  
      
    int max(int a, int b)   
    {  
        return (a>b)? a:b;  
    }  
      
    // Return the length of LCS for X[0...m-1] and Y[0...n-1]  
    int lcs(string &X, string &Y, int m, int n)  
    {  
        if (m == 0 || n == 0)  
            return 0;  
        if (X[m-1] == Y[n-1])  
            return lcs(X, Y, m-1, n-1) + 1;  
        else  
            return max(lcs(X, Y, m, n-1), lcs(X, Y, m-1, n));  
    }  
      
    int main()  
    {  
        string X = "ABCBDAB";  
        string Y = "BDCABA";  
      
        cout << "The length of LCS is " << lcs(X, Y, X.length(), Y.length());  
        cout << endl;  
      
        getchar();  
        return 0;  
    }  


像这样使用简单的递归,在最坏情况下(X 和 Y 的所有字符都不匹配,即LCS的长度为0)的时间复杂度为 θ(2n)。这和穷举法一样还是指数级的,太慢了。

根据程序中 X 和 Y 的初始值,我们画出部分递归树:


递归树中红框标记的部分被调用了两次。如果画出完整的递归树,我们会看到很多重复的调用,所以这个问题具有重叠子问题的特性。

③ 动态规划求解

简单的递归之所以和穷举法一样慢,因为在递归过程中进行了大量的重复调用。而动态规划就是要解决这个问题,通过用一个表来保存子问题的结果,避免重复的计算,以空间换时间。前面我们已经证明,最长公共子序列问题具有动态规划所要求的两个特性,所以 LCS 问题可以用动态规划来求解。

下面是用动态规划(打表)解决LCS问题:


C++代码:

[cpp] view plain copy 在CODE上查看代码片派生到我的代码片

    // 动态规划求解LCS问题  
    #include <iostream>  
    #include <string>  
    #include <vector>  
    using namespace std;  
      
    int max(int a, int b)   
    {  
        return (a>b)? a:b;  
    }  
      
    /** 
     * 返回X[0...m-1]和Y[0...n-1]的LCS的长度  
     */  
    int lcs(string &X, string &Y, int m, int n)  
    {  
        // 动态规划表,大小(m+1)*(n+1)  
        vector<vector<int>> table(m+1,vector<int>(n+1));    
      
        for(int i=0; i<m+1; ++i)  
        {  
            for(int j=0; j<n+1; ++j)  
            {  
                // 第一行和第一列置0  
                if (i == 0 || j == 0)  
                    table[i][j] = 0;  
      
                else if(X[i-1] == Y[j-1])  
                    table[i][j] = table[i-1][j-1] + 1;  
      
                else  
                    table[i][j] = max(table[i-1][j], table[i][j-1]);  
            }  
        }  
      
        return table[m][n];  
    }  
      
    int main()  
    {  
        string X = "ABCBDAB";  
        string Y = "BDCABA";  
      
        cout << "The length of LCS is " << lcs(X, Y, X.length(), Y.length());  
        cout << endl;  
      
        getchar();  
        return 0;  
    }  


容易看出,动态规划解决LCS问题的时间复杂度为 θ(mn),这比简单的递归实现要快多了。空间复杂度是θ(mn),因为使用了一个动态规划表。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值