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

来源:互联网 发布:java2游戏编程 编辑:程序博客网 时间:2024/06/05 00:19

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

修改于 2016-09-17 11:51:00

关键定义

  • 状态定义:设c[i, j]为子串Xi与Yj的LCS的长度,i和j都代表长度;

  • 状态转移方程:假定X与Y的LCS为Z,X={x1, x2, …, xm}Y={y1, y2, …, yn}Z={z1, z2, …, zk}.对于这个问题,可以分3种情况:

    1. 如果xm == yn,则表明xm与yn为两者LCS的其中一个元素,即zk == xm == yn,此时问题转变为求子串Xm - 1与Yn - 1的LCS了,因为Zk可以表示为Zk = LCS(Xm - 1, Yn - 1) + xm | yn

    2. 如果xm != yn && zk != xm,则表明xm不是两者LCS中的元素,即LCS存在于Xm - 1与Yn中,此时问题转变为求子串Xm - 1与Yn的LCS了;

    3. 如果xm != yn && zk != yn,则表明yn不是两者LCS中的元素,即LCS存在于Xm与Yn - 1中,此时问题转变为求子串Xm与Yn - 1的LCS了。

      对于2和3,这里要理解一点,在递归求解的过程中,xm != yn可以视为同一种情况,只是最后的LCS结果是在Xm - 1, YnXm, Yn - 1中取长度最大的那种情况,则状态方程可归纳如下:

      dp[i, j] = { 0, i = 0 || j = 0 } | { dp[i - 1, j - 1] + 1, X[i] = Y[j] } | { max(dp[i - 1, j], dp[i, j - 1]), X[i] != Y[j] }

  • 状态方程的解释:前两种情况都比较容易理解,最后一种则存在一个重叠的子问题,即计算i - 1, j与i, j - 1的LCS时,都会重复计算i - 1, j - 1的情况,所以这里就不需要再添加X[i] != Y[j]的情况了。

仅求LCS长度

​ 由上述定义,即可编写仅求LCS长度的代码了,具体代码如下:

int lcs(const char *str1, const char *str2){    int i, j;    for (i = 0; i <= strlen(str1); ++i) {        for (j = 0; j <= strlen(str2); ++j) {            //之所以要让str1[i - 1]与str2[j - 1]比较时,            //对应到dp[i][j],是因为状态方程中,dp[i][j]            //求值时,需要求dp[i - 1][j]、dp[i][j - 1]或            //dp[i - 1][j - 1],如此,若不将str中各元素位置            //后移一位对应dp的值,就会出现求dp[-1][-1]之类的            //越界情况,具体情况可以通过一张图来了解,图在代码下方            if (i == 0 || j == 0) {                dp[i][j] = 0;                flg[i][j] = 0;            }            else if (str1[i - 1] == str2[j - 1]) {                dp[i][j] = dp[i - 1][j - 1] + 1;                flg[i][j] = 1;            }            else if (dp[i - 1][j] >= dp[i][j - 1]) {                dp[i][j] = dp[i - 1][j];                flg[i][j] = 2;            }            else {                dp[i][j] = dp[i][j - 1];                flg[i][j] = 3;            }        }    }    return dp[strlen(str1)][strlen(str2)];}

这里写图片描述
​ 其中,flg相应项为1,则表示X[i - 1], Y[j - 1]为LCS中的元素;flg相应项为2,则表示LCS存在于Xi - 1, Yj中;flg相应项为3,则表示LCS存在于Xi, Yj - 1中。

获取LCS

​ 在上一步中,flg数组已经记录了LCS中包含的元素(有多种可能),若仅取其中一种,则可以使用递归,根据flg每个元素的值去决定是否输出相应字符串中的值,具体代码如下:

void print_lcs(const char *str, int i, int j){    if (i < 0 || j < 0) {        return ;    }    if (flg[i][j] == 1) {        print_lcs(str, i - 1, j - 1);        printf("%c ", str[i]);    }    else if (flg[i][j] == 2) {        print_lcs(str, i - 1, j);    }    else {        print_lcs(str, i, j - 1);    }}

​ 而对于要输出多种情况的问题,在下一篇中将有介绍。

0 0