最长公共子序列问题可以描述如下:
给定两个序列X[0,····m]和Y[0,····n],找出X和Y的最大长度的公共子序列S,即最长公共子序列问题(longest common sequence )。
分析:
假设Z[0,····k]是X和Y的一个LCS,那么存在如下规律:
1. 如果X[m] == Y[n] == Z[k], 那么Z[0,···k-1]为 X[0···m-1]和Y[0····n-1]的一个LCS。
2.如果X[m] != Y[n], 那么Z[k] != X[m] 意味着Z[0···k]为X[0···m-1]和Y[0····n]的一个LCS。
3.如果X[m] != Y[n], 那么Z[k] != Y[n] 意味着Z[0···k]为X[0···m]和Y[0····n-1]的一个LCS。
从以上三条就可以看出最长公共子序列具有最优子结构(即一个问题的最优解包含了子问题的最优解)。而最优子结构也是动态规划的一个基本要素。
根据上面的三条规律,可以知道如果要求得X = ( x1,x2,···,xm)和Y = ( y1,y2,···,yn)的一个LCS,那么就要考虑下面两种情况:
1.如果xm == yn,那么接下来就要求Xm-1和Yn-1的一个LCS,将Xm加上去就构成了X和Y的一个LCS。
2.如果xm != yn,那么就要求得Xm-1和Y的一个LCS,以及Xm和Yn-1的一个LCS,二者中较长的即X和Y的一个LCS。
从这里也可以看到该问题的重叠子问题性质(不同的子问题包含共同的子子问题),这也是动态规划的第二个要素。
通过上面的分析,可以确定LCS可以通过动态规划进行求解。
由LCS的最优子结构可以得到如下递归方程:
0 如果 i = 0 或 j = 0
C[i,j] = c[i-1,j-1] 如果i,j>0, xi == yj
Max(c[i-1,j], c[i, j-1]) 如果 i,j > 0 ,xi != yj
根据上面的递归方程,可以写出函数来计算LCS的长度。为了求出LCS,可以维护一个辅助的二维数组action,并定义一个枚举类型enum Action {both_erase, x_erase, y_erase }。
过程如下:
void lcs( char seq1[], char seq2[], int len[][length2+1], Action action[][length2], int len1, int len2 )
{
int i, j;
for( i = 0; i <= length1; ++i )
len[i][0] = 0;
for( j = 0; j <= length2; ++j )
len[0][j] = 0;
for( i = 0; i < length1; ++i )
for( j = 0; j < length2; ++j )
{
if( seq1[i] == seq2[j] )
{
len[i+1][j+1] = len[i][j] + 1;
action[i][j] = both_erase;
}else
{
len[i+1][j+1] = len[i+1][j] >= len[i][j+1] ? len[i+1][j] : len[i][j+1];
if( len[i+1][j+1] == len[i][j+1] ).
action[i][j] = x_erase;
else
action[i][j] = y_erase;
}
}
}
len[i][j]表示seq1[0···i-1]和seq2[0···j-1]两个序列之间的lcs的长度,action[i][j] 表示在输出seq1[0···i]和seq2[0···j]两个序列的lcs时应该采取的动作,以便后面可以根据action打印出LCS。
打印函数如下:
void print( char seq1[], Action action[][length2], int len1, int i, int j )
{
if( i >= 0 && j >= 0 )
{
if( action[i][j] == both_erase )
{
print( seq1, action, len1, i-1, j-1 );
cout << seq1[i];
}
else if( action[i][j] == x_erase )
print( seq1, action, len1, i-1, j );
else
print( seq1, action, len1, i, j-1 );
}
}
不需要辅助数组action的打印函数如下:
void print_without_ancillary_space( char seq1[], int len[][length2+1], int i , int j )
{
if( i > 0 && j > 0 )
{
if( len[i][j] == len[i-1][j] )
{
print_without_ancillary_space( seq1, len, i-1, j );
}else if ( len[i][j] == len[i][j-1] )
{
print_without_ancillary_space( seq1, len, i, j-1 );
}else if( len[i][j] == len[i-1][j-1] + 1 )
{
print_without_ancillary_space( seq1, len, i-1, j-1 );
cout << seq1[i-1];
}
}
}