1、基本概念
一个给定序列的子序列就是该给定序列中去掉零个或者多个元素的序列。形式化来讲就是:给定一个序列X={x1,x2,……,xm},另外一个序列Z={z1、z2、……,zk},如果存在X的一个严格递增小标序列<i1,i2……,ik>,使得对所有j=1,2,……k,有xij
= zj,则Z是X的子序列。例如:Z={B,C,D,B}是X={A,B,C,B,D,A,B}的一个子序列,相应的小标为<2,3,5,7>。从定义可以看出子序列直接的元素不一定是相邻的。
公共子序列:给定两个序列X和Y,如果Z既是X的一个子序列又是Y的一个子序列,则称序列Z是X和Y的公共子序列。例如:X={A,B,C,B,D,A,B},Y={B,D,C,A,B,A},则序列{B,C,A}是X和Y的一个公共子序列,但不不是最长公共子序列。
最长公共子序列(LCS)问题描述:给定两个序列X={x1,x2,……,xm}和Y={y1,y2,……,yn},找出X和Y的最长公共子序列。
2、动态规划解决过程
1)描述一个最长公共子序列
如果序列比较短,可以采用蛮力法枚举出X的所有子序列,然后检查是否是Y的子序列,并记录所发现的最长子序列。如果序列比较长,这种方法需要指数级时间,不切实际。
LCS的最优子结构定理:设X={x1,x2,……,xm}和Y={y1,y2,……,yn}为两个序列,并设Z={z1、z2、……,zk}为X和Y的任意一个LCS,则:
(1)如果xm=yn,那么zk=xm=yn,而且Zk-1是Xm-1和Yn-1的一个LCS。
(2)如果xm≠yn,那么zk≠xm蕴含Z是是Xm-1和Yn的一个LCS。
(3)如果xm≠yn,那么zk≠yn蕴含Z是是Xm和Yn-1的一个LCS。
定理说明两个序列的一个LCS也包含两个序列的前缀的一个LCS,即LCS问题具有最优子结构性质。
2)一个递归解
根据LCS的子结构可知,要找序列X和Y的LCS,根据xm与yn是否相等进行判断的,如果xm=yn则产生一个子问题,否则产生两个子问题。设C[i,j]为序列Xi和Yj的一个LCS的长度。如果i=0或者j=0,即一个序列的长度为0,则LCS的长度为0。LCS问题的最优子结构的递归式如下所示:
3)计算LCS的长度
采用动态规划自底向上计算解。书中给出了求解过程LCS_LENGTH,以两个序列为输入。将计算序列的长度保存到一个二维数组C[M][N]中,另外引入一个二维数组B[M][N]用来保存最优解的构造过程。M和N分别表示两个序列的长度。该过程的伪代码如下所示
注意我的代码中继续使用vector的vector来表示二维数组:
//最长公共子序列问题: //给定两个两个序列X = <x1, x2, ....., xm>和Y = <y1, y2, ...., yn>, 希望找出X和Y的最大长度的公共子序列,可以使用动态规划来解决 //定理15.1(LCS的最优子结构) //设X = <x1, x2, ....., xm>和Y = <y1, y2, ...., yn>为两个序列,并设Z = { z1, z2, ...., zk }为X和Y的任意一个LCS //(1)如果xm = yn, 那么zk = xm = yn, 而且Zk - 1是Xm - 1与Yn - 1的一个LCS //(2)如果xm != yn,那么Zk ! = xm蕴含Z是Xm - 1和Y的一个LCS //(3)如果xm != yn, 那么Zk ! = yn,蕴含Z是X和Yn - 1的一个LCS //定义c[i][j]为序列Xi与Yi的一个LCS长度,则有下面递归表达式 // 当i = 0 或 j = 0的时候 c[i][j]=0; //若i, j > 0 且xi = yi的时候 c[i][j] = c[i - 1][j - 1] + 1 //若i, j > 0 且 xi != yi c[i][j]= max(c[i][j - 1], c[i - 1][j]) //根据上面的递归式可以写出自底向上的动态规划代码 //author:ugly_chen(stallman) time:2014.12.15 0:51 //if you have any problems , pls ask me and leave some words, loughing....... #include<iostream> #include<string> #include<vector> #include<algorithm> using namespace std; pair<vector<vector<int>>,vector<vector<int>>> LCS_Length(string x, string y) { int m = x.size(); int n = y.size(); vector<vector<int>> c(m+1, vector<int>(n+1, 0)); vector<vector<int>> b(m+1, vector<int>(n+1, 0)); for (int i = 1; i <= m; ++i) { c[i][0] = 0; } for (int j = 1; j <= n; ++j) { c[0][j] = 0; } for (int i = 1; i <= m; ++i) { for (int j = 1; j <= n; ++j) { if (x[i - 1] == y[j - 1]) { c[i][j] = c[i - 1][j - 1] + 1; b[i][j] = 1;//这里我使用1表示箭头指向左上, } else if (c[i - 1][j] >= c[i][j - 1]) { c[i][j] = c[i - 1][j]; b[i][j] = 2;//这里我使用2表示箭头指向上 } else { c[i][j] = c[i][j - 1]; b[i][j] = 3;//这里我使用3表示箭头指向左 } } } return make_pair(c, b); } void print_LCS(vector<vector<int>>b, string x, int i, int j) { if (i == 0 || j == 0) return; if (b[i][j] == 1) { print_LCS(b, x, i - 1, j - 1); cout << x[i - 1]<<" "; } else if (b[i][j] == 2) print_LCS(b, x, i - 1, j); else print_LCS(b, x, i, j - 1); } int main() { string x = "ABCBDAB"; string y = "BDCABA"; int i = x.size(); int j = y.size(); vector<vector<int>> c = LCS_Length(x, y).first; vector<vector<int>> b = LCS_Length(x, y).second; cout << "the LCS is: " << c[x.size()][y.size()] << endl; print_LCS(b, x, i, j); return 0; }