动态规划--公共子序列LCS

一、问题描述:

  • 求两个字符串的最长公共子串的长度。两个字符串X和Z的公共子串是指X中按序取一些字符构成的子串与在Z中按同样方法得到的一个子串相同。
    例如:X=“abcdefghi”
    Z=“xyazdwf”
    则”a” “d” “f” “ad” “df” “adf”都是它们的公共子串,而最长公共子串的长度为3。

二、状态空间:

  • f(i, j)表示第一个字符串的前i个字符和第二个字符串的前j个字符的最长公共子串的长度。

三、状态转移方程:

  • f(i,j)={(f(i−1,j−1)+1 , (a[i]==b[j])
    max⁡(f(i−1,j),f(i,j−1)),(a[i]!=b[j]))}
  • 初始条件:f(i, 0)=f(0, j)=0。这里有效的i和j是从1开始的。
  • 结果:f(n, m)。n和m分别为两个字符串的长度。
  • 解释:前一个式子是如果a[i]=b[j],则dp[i][j]等于第一个序列的前i-1个字符和第二个序列前j-1个字符的最长公共子串 +1
    后一个式子是如果a[i]!=b[j],则dp[i][j]等于两者之间最大的

四、实现方法:

由于f(i,j)只和f(i-1,j-1), f(i-1,j)和f(i,j-1)有关, 而在计算f(i,j)时, 只要选择一个合适的顺序,就可以保证这三项都已经计算出来了,这样就可以计算出f(i,j)。这样一直推到f(n, m)就得到所要求的解了。

状态空间的定义方法:
一般二维数组(n*m)实现(可能会爆空间);
滚动数组实现,只需要存储两行数据(2*n)。复杂度:O(n*m)

五、LCS思考

1、如果要求最长公共子序列的个数,该如何处理?
设g(i, j)为第一个字符串的前i个字符和第二个字符串的前j个字符达到最长公共子串的长度(f(i, j),设为k)的字符串的个数,则有:
如果f(i-1, j)=k,则g(i, j) += g(i-1, j)
如果f(i, j-1)=k,则g(i, j) += g(i, j-1)
如果a(i)=b(j),则g(i, j) += g(i-1, j-1)
如果a(i)!=b(j), 且f(i-1,j-1)=k,则g(i, j) -= g(i-1, j-1)
最终结果为:g(n, m)

2、如果要输出一个最长公共子序列,又该如何处理?
方法:x从1到k,扫描f(i, j)即可。
如果要输出所有的最长公共子序列,又该如何处理?
方法:对f(i, j)用回溯法,从右下角开始。
如果a[i]=b[j],则a[i]为最长公共子序列的一个元素,下一步为(i-1, j-1)
如果a[i]!=b[j],下一步为f(i-1, j)和f(i,j-1)较大的方向。如果两者相等,则两个方向都要考虑。
直到i或j等于0为止。

六、LCS优化

举例说明:设有两个字符串:
    X:abdba
    Y:dbaaba
    先顺序扫描X串,取其在Y串的所有位置:
        a(2,3,5)  b(1,4)  d(0)。
    将每个字母的位置反序列,并替换X中的对应的字母,替换结果(5,3,2) (4,1) (0) (4,1) (5,3,2)
    则最终每个括号里选一个数构成的最长严格递增子序列的长度即为解。
    因此最大长度为3。

七、实现代码
1.二维数组实现(HDU1159 Common SubSequence

    #include<iostream>
    #include<algorithm>
    #include<cmath>
    #include<cstring>
    #include<cstdlib>
    #include<vector>
    using namespace std;

    /*
    编程人:zmh
    编程日:2018.1.24
    编程题:求两个序列的最长公共子序列 
    编程思路:f(i, j)表示第一个字符串的前i个字符和第二个字符串的前j个字符的最长公共子串的长度
            算法没有优化:
            状态转移方程:f[i][j] = f[i-1][j-1] + 1        (a[i] == b[i])
                        f[i][j] = MAX(f[i-1][j],f[i][j-1])   (a[i] != b[i])
    输入样例:
        abcfbc abfcab
        programming contest 
        abcd mnp 
    输出样例:
        4
        2
        0 
    AC
    */ 

    #define N 1001
    #define MAX(a,b) ((a)>(b)?(a):(b))

    string a,b;
    int lena,lenb;

    int f[N][N];

    int main(){
        while(cin>>a>>b){

            lena = a.length();
            lenb = b.length();
            memset(f,0,sizeof(f));

            for(int i=0;i<lena;i++){
                for(int j=0;j<lenb;j++){
                    if(a[i]==b[j]){
                        f[i+1][j+1] = f[i][j] + 1;
                    }else{
                        f[i+1][j+1] = MAX(f[i][j+1],f[i+1][j]);
                    }
                }
            }
            cout<<f[lena][lenb]<<endl;
        }


        return 0;
    } 

2.滚动数组空间优化(HDU1159 Common SubSequence

    #include<iostream>
    #include<algorithm>
    #include<cmath>
    #include<cstring>
    #include<cstdlib>
    #include<vector>
    using namespace std;

    /*
    编程人:zmh
    编程日:2018.1.24
    编程题:求两个序列的最长公共子序列 
    编程思路:f(i, j)表示第一个字符串的前i个字符和第二个字符串的前j个字符的最长公共子串的长度
            算法优化:使用 2*N 滚动数组对其进行空间优化 
            状态转移方程:f[i][j] = f[i-1][j-1] + 1        (a[i] == b[i])
                        f[i][j] = MAX(f[i-1][j],f[i][j-1])   (a[i] != b[i])
    */ 

    //这个数组开的大小导致 WR  
    #define N 1005
    #define MAX(a,b) ( (a)>(b)?(a):(b) )

    int dp[2][N];

    string a,b;

    int main(){

        while(cin>>a>>b){
            memset(dp,0,sizeof(dp));
            int lena = a.length();
            int lenb = b.length();

            int t = 0; 

            for(int i=0;i<lena;i++){
                for(int j=0;j<lenb;j++){
                    if(a[i] == b[j]){
                        dp[t][j+1] = dp[1-t][j] + 1;
                    }else{
                        dp[t][j+1] = MAX(dp[1-t][j+1],dp[t][j]);
                    }
                }
                t = 1 - t;
            }
            t = 1 - t;
            cout<<dp[t][lenb]<<endl;
        }

        return 0;
    } 

3.最长公共子序列的输出

    #include<iostream>
    #include<algorithm>
    #include<cmath>
    #include<cstring>
    #include<cstdlib>
    #include<vector>
    using namespace std;

    #define N 1001

    string a,b,ab;
    int lena,lenb,lenab;

    int dp[N][N];

    int vis[N][N];


    /*
    输出一个公共子序列,只要扫描f[i][j],输出每一行第一个变的位置对应的a序列的字符 
    */
    void dfs1(int la,int lb){
        int t,i,j;
        for(t = 1;t<=dp[la][lb];t++){
            for(i=1;i<=la;i++){
                for(j=1;j<=lb;j++){
                    if(dp[i][j] == t){
                        cout<<a[i-1];
                        break;
                    }
                }
                if(j<=lb){
                    break;
                }
            }       
        }
    }

    int main(){
        while(cin>>a>>b){
            lena = a.length();
            lenb = b.length();

            memset(dp,0,sizeof(dp));
            memset(vis,0,sizeof(vis));

            for(int i=0;i<=lena;i++)
                vis[i][0] = 1;
            for(int i=0;i<=lenb;i++)
                vis[0][i] = 2;


            for(int i=1;i<=lena;i++){
                for(int j=1;j<=lenb;j++){
                    if(a[i-1] == b[j-1]){
                        dp[i][j] = dp[i-1][j-1] + 1;
                        vis[i][j] = 0;
                    }else{
                        if(dp[i-1][j]>dp[i][j-1]){
                            dp[i][j] = dp[i-1][j];
                            vis[i][j] = 1;
                        }else{
                            dp[i][j] = dp[i][j-1];
                            vis[i][j] = 2;
                        }
                    }
                }
            }

            dfs1(lena,lenb);//输出一个公共子串

            cout<<endl;

        }

        return 0;
    } 

4.LCS的应用,最长公共子序列的输出(HDU1503 Advanced Fruits)

    #include<iostream>
    #include<algorithm>
    #include<cmath>
    #include<cstring>
    #include<cstdlib>
    #include<vector>
    using namespace std;

    #define N 1001

    string a,b,ab;
    int lena,lenb,lenab;

    int dp[N][N];

    int vis[N][N];

    void dfs2(int la,int lb){
        if(!la&&!lb){//此处导致一直运行不对 
            return;
        } 
        if(vis[la][lb] == 1){
            dfs2(la-1,lb);
            cout<<a[la-1];
        }else if(vis[la][lb] == 2){
            dfs2(la,lb-1);
            cout<<b[lb-1];
        }else{
            dfs2(la-1,lb-1);
            cout<<a[la-1];
        }
        return;
    }

    int main(){
        while(cin>>a>>b){
            lena = a.length();
            lenb = b.length();

            memset(dp,0,sizeof(dp));
            memset(vis,0,sizeof(vis));

            for(int i=0;i<=lena;i++)
                vis[i][0] = 1;
            for(int i=0;i<=lenb;i++)
                vis[0][i] = 2;


            for(int i=1;i<=lena;i++){
                for(int j=1;j<=lenb;j++){
                    if(a[i-1] == b[j-1]){
                        dp[i][j] = dp[i-1][j-1] + 1;
                        vis[i][j] = 0;
                    }else{
                        if(dp[i-1][j]>dp[i][j-1]){
                            dp[i][j] = dp[i-1][j];
                            vis[i][j] = 1;
                        }else{
                            dp[i][j] = dp[i][j-1];
                            vis[i][j] = 2;
                        }
                    }
                }
            }
            dfs2(lena,lenb);
            cout<<endl;     
        }   
        return 0;
    } 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值