动态规划—最长公共子序列LCS

来源:互联网 发布:linux iso文件怎么挂载 编辑:程序博客网 时间:2024/05/24 04:50

      

  最长公共子序列问题可以描述如下:

给定两个序列X[0,····m]Y[0,····n],找出XY的最大长度的公共子序列S,即最长公共子序列问题(longest common sequence )。

分析:

         假设Z[0,····k]XY的一个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,二者中较长的即XY的一个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])    如果 ij > 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;elseaction[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 );elseprint( 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];}}}
遇到了几个问题:
1.在lcs函数中的第一个else分支中,为得到action的值,必须先判断len[i+1][j+1]和len[i][j+1]的关系.如果先判断len[i+1][j+1]和len[i+1][j]的关系,程序会出错,原因还没想清楚。
2.c++中所有字符串字面值都有编译器在末尾自动添加空字符。
如果需要程序文件,点击这里
原创粉丝点击