动态规划(一)——LCS问题

注:

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

源码: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("\n");
    for (i = m0; i < m; i++)
    {
        printf("%-4c", x[i - m0]);
        for (j = n0; j < n; j++)
        {
            printf("%-3d", mat[i][j]);
        }
        printf("\n");
    }
}

/*
 * > 子串是串的一个连续的部分
 * > 子序列则是不改变序列的顺序,而从序列中去掉任意的元素而获得新的序列
 * > 【也就是说,子串中字符的位置必须是连续的,子序列则可以不必连续。】
 */

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("\n");
        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 = ‘\0‘;
    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\n");
    print_matrix(zmat, x, y, 1, 1, m + 1, n + 1);
    printf("Trace matrix\n");
    print_matrix(pmat, x, y, 0, 0, m, n);

    //跟踪开始
    node root;
    root.parent = -1;
    root.c = ‘\0‘;
    tree.push_back(root);
    printf("------------- Result -------------\n");
    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("\n");
    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("\n");
    }
    printf("------------- Result -------------\n");
    for (auto& v : str_indexes)
    {
        for (j = v; j < max + v; j++)
        {
            printf("%c", y[j]);
        }
        printf("\n");
    }
    delete[] z;
}

int main(int argc, char* argv[])
{
    char *str1 = "BADCDCBA";
    char *str2 = "ABCDCDAB";
    printf("a: %s\n", str1);
    printf("b: %s\n", str2);
    printf("========= lcseq ==========\n");
    lcseq(str1, str2);
    printf("\n");
    printf("========= lcstr ==========\n");
    lcstr(str1, str2);
    printf("\n");
    return 0;
}
时间: 2024-08-01 19:56:48

动态规划(一)——LCS问题的相关文章

非动态规划实现LCS算法

LCS(最长公共子串 longest common subsequence)一般都会采用动态规划的算法来实现,算法的时间复杂度大概是O(x2),  另外需要一个x2的额外空间, 这个算法这里我不做说明,给个讲得不错的教程地址 LCS教程 这边博文里我将给出一个不采用动态规划的算法,并且时间复杂度和动态规划算法相同,还不会使用到额外的空间,空间复杂度为O(0). 思路: 假设存在两个字符串l和r, 并且strlen(l) <= strlen(r), 将l字符串想象成一辆车, 将r字符串想象成公路,

poj1159(动态规划或者lcs求最长字串)

http://acm.hrbust.edu.cn/vj/index.php?c=problem-problem&id=20480 LCS求最长公共子串 #include<stdio.h> #include<iostream> #include<map> #include<string.h> #include<vector> using namespace std; short a[5005][5005]; char s1[5005]; c

一些常见的递归算法 动态规划算法

最大值的递归算法 对于一个数组 有A[ 1...n ] 算法调用的时候调用max(n) max(i) if i = 1 return A[i] else if A[i]>max(i-1) return A[i] else return max(i-1) end if end if 平均值的递归算法 对于数组 a[ 1...n ] sum 初值为 0 index 初值则为1 调用该算法使用 Ave(a,sum,index) Ave(int a[],int sum,int index) if(ind

POJ 1458 - Common Subsequence(最长公共子序列) 题解

此文为博主原创题解,转载时请通知博主,并把原文链接放在正文醒目位置. 题目链接:http://poj.org/problem?id=1458 题目大意: 有若干组数据,每组给出两个字符串(中间用任意数量的空格间隔),输出这两个字符串最长公共子序列的长度.每次输出后换行. 分析: 动态规划求LCS,f[i][j]表示第一个字符串匹配到第i位,第二个字符串匹配到第j位时最长公共子序列的长度. 转移方程:当a[i] = b[i]时,f[i][j] = f[i-1][j-1]+1,其他情况时f[i][j

VS2017gets的使用

由于动态规划的LCS问题,需要从第一个字符开始读取比较方便.所以用gets_s();第一个参数是起始位置,第二个参数是字读取字符的长度. #include<bits/stdc++.h> #include<cstdio> using namespace std; const int N = 100; char A[N], B[N]; int dp[N][N]; int main() { int n; gets_s(A + 1,105); gets_s(B + 1, 105); int

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

tips : 本文内容是参考了很多著名博客和自己的思考得出的,如有不当欢迎拍砖. 先简单说一下动态规划 通俗地说:动态规划就是将一个可以划分为子问题的问题进行递归求解,不过动态规划将大量的中间结果保存起来, 不管它们是否会用得到,从而在后面的递归求解过程中可以快速求解.由此可以看得出来动态规划是一个以牺牲空间 为代价换取时间的算法. 对于最长公共子序列的题目不用多说,现在来分析一下LCS的动态规划解决思路: 一.首先先观察问题是否符合动态规划最明显的两个特征:最优子结构和重叠子问题 方便起见,以

POJ1080 Human Gene Functions 动态规划 LCS的变形

题意读了半年,唉,给你两串字符,然后长度不同,你可以用'-'把它们补成相同长度,补在哪里取决于得分,它会给你一个得分表,问你最大得分 跟LCS很像的DP数组 dp[i][j]表示第一个字符串取第i个元素第二个字符串取第三个元素,然后再预处理一个得分表加上即可 得分表: score['A']['A'] = score['C']['C'] = score['G']['G'] = score['T']['T'] = 5; score['A']['C'] = score['C']['A'] = -1;

动态规划算法之:最长公共子序列 & 最长公共子串(LCS)

1.先科普下最长公共子序列 & 最长公共子串的区别: 找两个字符串的最长公共子串,这个子串要求在原字符串中是连续的.而最长公共子序列则并不要求连续. 2.最长公共子串 其实这是一个序贯决策问题,可以用动态规划来求解.我们采用一个二维矩阵来记录中间的结果.这个二维矩阵怎么构造呢?直接举个例子吧:"bab"和"caba"(当然我们现在一眼就可以看出来最长公共子串是"ba"或"ab") b a b c 0 0 0 a 0 1

动态规划 LCS,LIS

1.最大连续子序列 dp[i]=max(dp[i-1]+a[i],a[i]) 以i为结尾 2.最大不连续子序列 dp[i]=max(dp[j]+a[i],dp[j]) 3.最大连续递增子序列 if a[i]>a[j] dp[i]=max(dp[i-1]+a[i],a[i]) 4.最大不连续递增子序列 if a[i]>a[j] dp[i]=max(dp[j]+a[i],dp[j]) 5.最长不连续公共子序列 if a[i-1]==b[j-1] dp[i][j]=dp[i-1][j-1]+1; e

《算法导论》读书笔记之动态规划—最长公共子序列 &amp; 最长公共子串(LCS)

From:http://my.oschina.net/leejun2005/blog/117167 1.先科普下最长公共子序列 & 最长公共子串的区别: 找两个字符串的最长公共子串,这个子串要求在原字符串中是连续的.而最长公共子序列则并不要求连续. 2.最长公共子串 其实这是一个序贯决策问题,可以用动态规划来求解.我们采用一个二维矩阵来记录中间的结果.这个二维矩阵怎么构造呢?直接举个例子吧:"bab"和"caba"(当然我们现在一眼就可以看出来最长公共子串是