本课程是从少年编程网转载的课程,目标是向中学生详细介绍计算机比赛涉及的编程语言,数据结构和算法。编程学习最好使用计算机,请登陆 www.3dian14.org (免费注册,免费学习)。
在前一课中,我们讨论了最长公共子序列(LCS)问题,并且讨论了如何计算两个序列的LCS的长度,但是没有输出具体的LCS序列。在本课中,我们讨论了如何构造和输出LCS具体序列。
【问题】
给定两个序列,请找出两个序列中存在的最长公共子序列(LCS)。
注意:子序列由原序列中的子元素组成,这些子元素的次序与它们在原序列中出现的次序一样,但不一定连续。而公共子序列是指同时存在于两个已知序列中的子序列,其中长度最长的公共子序列就称为LCS(Longest Common Subsequence)。
【例1】
输入:序列“ABCDGH”和“ AEDFHR”
输出:子序列“ADH”同时出现在这两个序列中,并且是最长的公共子序列,长度为3。
【例2】
输入:序列“AGGTAB”和“ GXTXAYB”
输出:子序列“GTAB”,同时出现在这两个序列中,并且是最长的公共子序列,长度为4。
算法
我们在上一课输出LCS长度的算法基础上,做进一步的改进,输出具体的LCS内容。
令输入序列分别为长度为m和n的两个数组X[0..m-1]和Y[0..n-1]。
我们使用与上一课中一样定义的二维数组L[][],用来记录序列X和序列Y的LCS的长度。
令L(X[0..m-1], Y[0..n-1]) 为两个序列X和Y的LCS的长度。
L(X[0..m-1], Y[0..n-1]) 的递归定义如下:
1)如果两个序列的最后一个字符都匹配(或 X[m-1] == Y[n-1]),则L(X[0..m-1], Y[0..n-1]) = 1+L(X[0..m-2], Y[0..n-2])
2)如果两个序列的最后一个字符都不匹配(或 X [m-1]!= Y [n-1]),则 L(X[0..m-1], Y[0..n-1]) = MAX(L(X[0..m-2], Y[0..n-1]), L(X[0..m-1], Y[0..n-2]))算法的具体描述如下:
1)然后使用前一课讨论的步骤构造二维数组L[m+1][n+1]。
以X=“AGGTAB",Y="GXTXAYB"为例,得到的L数组如下所示。
2)值L[m][n]包含LCS的长度。创建一个字符数组lcs[],用来存放找到的LCS序列,lcs数组的长度等于lcs的长度加1(数组最后一个额外的字符用于存储\0,表示字符串的终结)。
3)从L[m][n]开始遍历二维数组。对每个单元格L[i][j]进行跟踪
a)如果与L[i][j]对应的字符(在X和Y中)相同(或X[i-1] == Y[j-1]),则将此字符作为LCS的一部分 。
b)否则比较L[i-1][j]和L[i][j-1]的值并朝更大值的方向前进。
lcs的构造过程如下所示。得到的LCS为“GTAB”。
算法实现
下面是上述算法的C++实现的一个例子。
#include
#include
#include
using namespace std;
/* 返回 X[0..m-1], Y[0..n-1]中的LCS长度 */
void lcs( char *X, char *Y, int m, int n )
{
int L[m+1][n+1];
/*自底向上构造L[m+1][n+1],L[i][j]包含 X[0..i-1] 和Y[0..j-1]的LCS的长度
for (int i=0; i<=m; i++)
{
for (int j=0; j<=n; j++)
{
if (i == 0 || j == 0)
L[i][j] = 0;
else if (X[i-1] == Y[j-1])
L[i][j] = L[i-1][j-1] + 1;
else
L[i][j] = max(L[i-1][j], L[i][j-1]);
}
}
//输出LCS
int index = L[m][n];
// 数组lcs存放LCS
char lcs[index+1];
lcs[index] = '\0'; // 结束字符
//以右下到左上的次序遍历数组L取出lcs中的字符
int i = m, j = n;
while (i > 0 && j > 0)
{
//如果X[]和Y[]中的当前字符相等,取出当前字符放入lcs
if (X[i-1] == Y[j-1])
{
lcs[index-1] = X[i-1];
i--; j--; index--;
}
//如果X[]和Y[]中的当前字符不相等,向大值方向移动
else if (L[i-1][j] > L[i][j-1])
i--;
else
j--;
}
// 输出lcs结果
cout << "LCS of " << X << " and " << Y << " is " << lcs;
}
/* 主程序 */
int main()
{
char X[] = "AGGTAB";
char Y[] = "GXTXAYB";
int m = strlen(X);
int n = strlen(Y);
lcs(X, Y, m, n);
return 0;
}
请输入上述程序并验证。
【思考题】上述方法只是找出了一个LCS序列,但是没有输出所有的LCS(因为两个序列X和Y可能有两个或两个以上的长度相等的LCS)。请您思考如何上述算法的基础上,编写程序找出序列X和序列Y的所有的LCS序列。我们将在下一课进行讲解。