【经典问题】二维动态规划问题:求最长公共子序列LCS

原博地址:http://blog.csdn.net/yysdsyl/article/details/4226630

                 http://blog.csdn.net/ljyljyok/article/details/77905681

    证明:   http://blog.csdn.net/waltonhuang/article/details/52032463


最长公共子序列问题是一个经典的计算机科学问题,也是数据比较程序,比如Diff工具,和生物信息学应用的基础。它也被广泛地应用在版本控制,比如Git用来调和文件之间的改变。

对于任意数量的序列的LCS问题属于NP-hard,但对于两个已知长度(m、n)的序列,可以用动态规划的思想在多项式时间内解决。

假设两个字符串一个为A[n],另一个为B[m],引入二维数组c[  ][  ],用c[ i ][ j ]记录A[ i ]与A[ j ]的LCS长度,那么c[ i ][ j ]的递推公式为:

if(i==0||j==0)
    c[i][j] = 0;        //边界初始化
else if(A[i]==B[j])     //子串的最后一个字符相等
    c[i][j] = c[i-1][j-1] + 1;    //LCS的长度必然要比去掉这个相等的字符时的子串的LCS长度大1
else                    //子串的最后一个字符不相等
    c[i][j] = max( c[i-1][j] , c[i][j-1] );    //LCS的长度必然 大于等于 去掉A中最后一个字符或去掉B中最后一个字符时的子串的LCS的长度

这个状态转移方程的证明如下:(转载自http://blog.csdn.net/waltonhuang/article/details/52032463)

  • A[i] == B[j]时 
    显然,由于两个字符串的最后一位相同,S[i][j]的长度应该比S[i-1][j-1]大1.

  • A[i] != B[j]时 
    结论是:c[i][j] = max(c[i-1][j], c[i][j-1])

    • 证明:

      1. 显然,由于添加了一个字符,c[i][j] 肯定大于等于c[i-1][j],也肯定大于等于c[i][j-1]
      2. 但是,c[i][j] 不能同时大于 c[i-1][j]c[i][j-1]。 
        反证法: 
        1.1. 如果c[i][j] > c[i-1][j],说明S[i][j]的最后一位是A[i]。(因为多了A[ i ]这个字符之后,最长公共子序列的长度多了1,所以多出来的这一位就是A[ i ]!) 
        1.2. 同理,如果c[i][j] > c[i][j-1],说明S[i][j]的最后一位是B[j]。 
        1.3. 那么如果c[i][j] 同时大于 c[i-1][j]c[i][j-1],说明S[i][j]的最后一位既是A[i],也是B[j]。那么A[i]就与B[j]相同了!矛盾了!所以反证出: c[i][j] 不能同时大于 c[i-1][j]c[i][j-1]。

现在举例说明,假设string A = "ABCBDAB",string B = "BDCABA",那么A.size()=7,B.size()=6,引入c[7+1][6+1]=c[8][7],多一个空间是为了存放边界值,即i=0或j=0时LCS都等于0。

然后排表如下图所示:(计算顺序是从i=1到i=7,以及j=1到j=6的)

【经典问题】二维动态规划问题:求最长公共子序列LCS_第1张图片

可以看到,最右下角的c[7][6] = 4就是要求的LCS的值了。

代码如下:

class LCS {
public:
    int findLCS(string A, int n, string B, int m) {
        // write code here
        int c[n+1][m+1];
        int i,j;
        memset(c, 0, sizeof(c));
        for(i=1;i<=n;++i){    // i=0或j=0默认初始化为0
            for(j=1;j<=m;++j){
                if(A[i-1]==B[j-1])
                    c[i][j] = c[i-1][j-1] + 1;
                else
                    c[i][j] = max(c[i-1][j],c[i][j-1]);
            }
        }
        return c[n][m];
    }
};

如果要求输出这个最长公共子序列,则需要通过数组c进行回溯:

    string lcstr;
    i = n;    //将i、j下标落到数组c的末尾元素上。
    j = m;
    while (i != 0 && j != 0)
    {
        if (A[i] == B[j]) {   //将相同的子元素压栈。然后指针前移,直到i、j指向0终止(因为任何字符串 与0 求公共子序列,都是0)
            lcstr.push_back(A[i]);
            --i;
            --j;
        }
        else { //若二者不相等,而最长公共子序列一定是由LCS(c[i][j-1] or c[i-1][j])的较大者得来,故将较大者的指针前移,接着遍历。
            if (c[i][j - 1] > c[i - 1][j]) {
                --j;        //将当前列前移到j-1列
            }
            else { // if(c[i][j - 1] <= c[i - 1][j])
                --i;
            }
        }
    }
    //求LCS(之一)
    reverse(lcstr.begin(), lcstr.end());

你可能感兴趣的:(动态规划,字符串)