zoukankan      html  css  js  c++  java
  • 动态规划(一)——最长公共子序列和最长公共子串

    注:

    • 最长公共子序列采用动态规划解决,由于子问题重叠,故采用数组缓存结果,保存最佳取值方向。输出结果时,则自顶向下建立二叉树,自底向上输出,则这过程中没有分叉路,结果唯一。
    • 最长公共子串采用参考串方式,逐步比对,若相同则对应参考值自增,同时记录当前时刻最大参考值,及其位置。最后输出多组结果。

    源码:lcs.cpp

    #include "stdafx.h"
    #include <stdio.h>
    #include <vector>
    
    /************************************************************************/
    /* 最长公共子序列 / 最长公共子串                                        */
    /*     lcseq      /     lcstr                                           */
    /************************************************************************/
    
    //注意!需要输出多组结果!
    
    int length(char *pstr)
    {
        int i;
        i = 0;
        while (*pstr++) i++;
        return i;
    }
    
    void print_matrix(int **mat, char *x, char *y, int m0, int n0, int m, int n)
    {
        int i, j;
        printf("    ");
        for (j = 0; j < n; j++)
        {
            printf("%-3c", y[j]);
        }
        printf("
    ");
        for (i = m0; i < m; i++)
        {
            printf("%-4c", x[i - m0]);
            for (j = n0; j < n; j++)
            {
                printf("%-3d", mat[i][j]);
            }
            printf("
    ");
        }
    }
    
    /*
     * > 子串是串的一个连续的部分
     * > 子序列则是不改变序列的顺序,而从序列中去掉任意的元素而获得新的序列
     * > 【也就是说,子串中字符的位置必须是连续的,子序列则可以不必连续。】
     */
    
    struct node
    {
        int parent;
        char c;
    };
    
    //************************************
    // Method:    构建二叉树
    // FullName:  build_btree
    // Access:    public 
    // Returns:   void
    // Qualifier:
    // Parameter: char * x 原始字符串X
    // Parameter: int * * mat 跟踪矩阵
    // Parameter: int m 串X长度
    // Parameter: int n 串Y长度
    // Parameter: int parent 父结点ID
    // Parameter: std::stack<node> & tree 树
    //************************************
    void build_btree(char *x, int **mat, int m, int n, int parent, std::vector<node>& tree)
    {
        if (m == -1 || n == -1)//跟踪到叶子结点,可以打印
        {
            int i;
            for (i = parent; i != -1; i = tree[i].parent)
            {
                char c;
                c = tree[i].c;
                if (c)
                {
                    printf("%c", c);
                }
            }
            printf("
    ");
            return;
        }
        if (m == 0 && n == 0)//处理左上角结点的情况Z(0,0)
        {
            build_btree(x, mat, -1, -1, parent, tree);
            return;
        }
        int p = mat[m][n];
        if (mat[m][n] == 0)//防止访问重复结点
            return;
        mat[m][n] = 0;
        node newnode;
        newnode.c = '';
        newnode.parent = parent;
        tree.push_back(newnode);
        parent = tree.size() - 1;
        switch (p)
        {
        case 1://左上,相同
            tree.back().c = x[m];
            build_btree(x, mat, m, n - 1, parent, tree);
            break;
        case 2://
            build_btree(x, mat, m - 1, n, parent, tree);
            break;
        case 3://
            build_btree(x, mat, m, n - 1, parent, tree);
            break;
        case 4://左和上
            build_btree(x, mat, m, n - 1, parent, tree);
            build_btree(x, mat, m - 1, n, parent, tree);
            break;
        default:
            break;
        }
    }
    
    //************************************
    // Method:    最长公共子序列(DP动态规划)
    // FullName:  lcseq
    // Access:    public 
    // Returns:   void
    // Qualifier:
    // Parameter: char * x
    // Parameter: char * y
    //************************************
    void lcseq(char *x, char *y)
    {
        //串x:X[ 0 .. m-1 ] x.length=m
        //串y:Y[ 0 .. n-1 ] y.length=n
        //串z:Z[ 0 .. k-1 ] z.length=k, z = lcseq(x,y)
    
        //【z是x与y的最长公共子序列】
        //
        //================ 递推方法 ================
        //从最后一位看起:
        //
        // >>> 第一种情况:
        // *     若X[last]==Y[last],则z[last]=X[last]=Y[last]
        // *     则lcseq(x,y) = lcseq(x-1,y-1) + (x[last] or y[last])
        //
        // >>> 第二种情况:
        // *     若X[last]<>Y[last],考虑:
        // *     1) 若Z[last]==X[last],则lcseq(x,y) = lcseq(x,y-1)
        // *     2) 若Z[last]==Y[last],则lcseq(x,y) = lcseq(x-1,y)
        // *     3) 否则,lcseq(x,y) = max_length{lcseq(x,y-1), lcseq(x-1,y)}
        // *     综合得lcseq(x,y) = max_length{lcseq(x,y-1), lcseq(x-1,y)}
        //
        //图表展示:
        //     ┏━━━━━━━┳━━━━━━━┓ 
        //     ┃  Z(i-1,j-1)  ┃   Z(i-1,j)   ┃
        //     ┣━━━━━━━╋━━━━━━━┫
        //     ┃  Z(i,j-1)    ┃   Z(i,j)     ┃
        //     ┗━━━━━━━┻━━━━━━━┛
        //
        //================ 总结递推公式 ================
        //     【已知Z(i-1,j-1)、Z(i-1,j)、Z(i,j-1),求Z(i,j)】
        //     初始:
        //     1) Z(0,0)=0
        //     2) Z(0,~)=0
        //     3) Z(~,0)=0
        //     递推:
        //     1) X[i] == Y[j] ==> Z(i,j) = Z(i-1,j-1) + 1
        //     2) X[i] <> Y[j] ==> Z(i,j) = max{Z(x,y-1), Z(x-1,y)}
        //
        //================ 跟踪输出所有LCS ================
        //由Z(m,n)可以向左上方遍历,所过所有路径即形成二叉树
        //故考虑建立二叉树,实行自叶子向根部的遍历
    
        int m, n, i, j;
        int **zmat, *zmat0;
        int **pmat, *pmat0;
        std::vector<node> tree;//保存二叉树的数组(栈式存储)
        //计算长度
        m = length(x);
        n = length(y);
        //建立保存Z(i,j)函数值的二维数组,定义域为{(i,j)|i in [0..m] and j in [0..n]}
        zmat = new int*[m + 1];//指针数组
        zmat0 = new int[(m + 1) * (n + 1)];//m+1行n+1列,一维数组建立
        for (i = 0; i <= m; i++) zmat[i] = &zmat0[i * (n+1)];
        for (i = 0; i <= m; i++) zmat[i][0] = 0;//i行0列置0
        for (j = 1; j <= n; j++) zmat[0][j] = 0;//0行j列置0
        //建立跟踪数组,跟踪Z(i,j)的取值方向(第一种情况为左上角取值,第二种情况为上边或左边取值)
        pmat = new int*[m];//指针数组
        pmat0 = new int[m * n];//m行n列,一维数组建立
        for (i = 0; i < m; i++) pmat[i] = &pmat0[i * n];
        //填充剩余的Z数组
        for (i = 1; i <= m; i++)
        {
            for (j = 1; j <= n; j++)//注意i,j范围
            {
                if (x[i - 1] == y[j - 1])//X[i] == Y[j] ==> Z(i,j) = Z(i-1,j-1) + 1
                {
                    zmat[i][j] = zmat[i - 1][j - 1] + 1;
                    pmat[i - 1][j - 1] = 1;//左上角取值路线
                }
                else//X[i] <> Y[j] ==> Z(i,j) = max{Z(x,y-1), Z(x-1,y)}
                {
                    int sub;
                    sub = zmat[i - 1][j] - zmat[i][j - 1];
                    if (sub >= 0)
                    {
                        zmat[i][j] = zmat[i - 1][j];
                        pmat[i - 1][j - 1] = sub == 0 ? 4 : 2;//左或上取值路线
                    }
                    else
                    {
                        zmat[i][j] = zmat[i][j - 1];
                        pmat[i - 1][j - 1] = 3;//左边取值路线
                    }
                }
            }
        }
        printf("Z matrix
    ");
        print_matrix(zmat, x, y, 1, 1, m + 1, n + 1);
        printf("Trace matrix
    ");
        print_matrix(pmat, x, y, 0, 0, m, n);
    
        //跟踪开始
        node root;
        root.parent = -1;
        root.c = '';
        tree.push_back(root);
        printf("------------- Result -------------
    ");
        build_btree(x, pmat, m - 1, n - 1, 0, tree);
        //清理工作
        delete[] zmat;
        delete[] zmat0;
        delete[] pmat;
        delete[] pmat0;
    }
    
    
    //************************************
    // Method:    最长公共子串(DP动态规划)
    // FullName:  lcstr
    // Access:    public 
    // Returns:   void
    // Qualifier:
    // Parameter: char * x
    // Parameter: char * y
    //************************************
    void lcstr(char *x, char *y)
    {
        //以Y作参考串,建立参考数组Z,然后遍历X
        //即:对所有i,存在j,使X[i]==Y[j],则Z'[j]=Z[j]+1
        //跟踪Z中最大值
        int i, j, m, n, *z, max;
        std::vector<int> str_indexes;//记录符合要求的lcstr起始位置(可能有多个)
        m = length(x);
        n = length(y);//Y作参考串
        z = new int[n + 1];
        for (j = 0; j <= n; j++) z[j] = 0;//初始化
        max = 0;
        printf("    ");
        for (j = 0; j < n; j++)
        {
            printf("%-3c", y[j]);
        }
        printf("
    ");
        for (i = 0; i < m; i++)
        {
            char c;
            c = x[i];
            printf("%-4c", c);
            for (j = n - 1; j >= 0; j--)
            {
                if (c == y[j])
                {
                    z[j + 1] = z[j] + 1;
                    int zj = z[j + 1];
                    if (zj > max)
                    {
                        max = zj;
                        str_indexes.clear();
                    }
                    if (zj == max)
                    {
                        str_indexes.push_back(j - zj);
                    }
                }
                else
                {
                    z[j + 1] = 0;
                }
            }
            for (j = 1; j <= n; j++)
            {
                printf("%-3d", z[j]);
            }
            printf("
    ");
        }
        printf("------------- Result -------------
    ");
        for (auto& v : str_indexes)
        {
            for (j = v; j < max + v; j++)
            {
                printf("%c", y[j]);
            }
            printf("
    ");
        }
        delete[] z;
    }
    
    int main(int argc, char* argv[])
    {
        char *str1 = "BADCDCBA";
        char *str2 = "ABCDCDAB";
        printf("a: %s
    ", str1);
        printf("b: %s
    ", str2);
        printf("========= lcseq ==========
    ");
        lcseq(str1, str2);
        printf("
    ");
        printf("========= lcstr ==========
    ");
        lcstr(str1, str2);
        printf("
    ");
        return 0;
    }
  • 相关阅读:
    JSP_EL使用
    Ajax乱码问题
    Myeclipse安装svn插件(link方式)
    JAVA多线程通信
    Java序列化与反序列化(Serializable)
    Java 字符流实现文件读写操作(FileReader-FileWriter)
    Java 字节流实现文件读写操作(InputStream-OutputStream)
    JAVA环境变量配置
    Flex设置外部浏览器
    J2EE5(Servlet2.5)对EL表达式的支持
  • 原文地址:https://www.cnblogs.com/bajdcc/p/4769334.html
Copyright © 2011-2022 走看看