目录
1 子序列概念
一个给定序列的子序列是在序列中删除若干个元素后得到的序列。在这里,首先说明子序列的概念(切记子序列非子集的概念),例如是序列的一个子序列,则序列在序列中相对应的下标为,序列和的下标都是从1开始。
2 问题描述
如果给定两个序列和序列,当另外一个序列即是的子序列又是的子序列,那么称序列是序列和序列的公共子序列。
假如,这两个序列,则序列是和的一个公共子序列,但他不是一个最长的公共子序列。而序列才是一个最长的公共子序列,他的长度为4,应为序列和没有长度大于四的公共子序列。
了解了最长公共子序列,那么最长公共子序列问题就是在给定的两个序列和中,找出他们最长的公共子序列这样一个问题。
2.1 问题分析:
设序列和序列的最长公共子序列是。
(1)如果,则,并且是和的最长公共子序列。
(2)如果并且,那么是和的一个最长公共子序列。
(3)如果并且,那么是和的一个最长公共子序列。
在这里,其中;;.
2.2 动态规划求解公式
根据以上问题分析,我们要找出序列和的最长公共子序列,可以按照以下递归进行求解:当时,(即两个序列最后一个字符相同),那我们的变为求解和得最长公共子序列在加上即可,就可以得到和的一个最长公共子序列。当时,必须求解两个问题,即找出和,和两者当中较长的一个公共子序列 ,即得到最终结果,所以我们建立了以上的递归公式求解。我们用记录序列和的最长公共序列的长度,其中当i=0或者j=0时表示此时最长公共序列为。记录的值是由哪一个子问题求解得到的。和的最长公共子序列记录在。
下面我们就可以写出求最长公共子序列的递推公式:
2.3 算法展示
#include "pch.h"
#include <iostream>
using namespace std;
int LengthString(char *x, char *y, int m, int n, int **c, int **b)
{
//x和y是两个字符串,m和n分别是其长度,二维数组保存最长公共序列长度,b数组记录在哪个子问题下得到的解
for (int i = 0;i <= m;i++)
c[i][0] = 0;
for (int j = 0;j <= n;j++)
c[0][j] = 0;
for (int i = 1;i <= m;i++)
{
for (int j = 1;j <= n;j++)
{
if (x[i] == y[j])
{
c[i][j] = c[i - 1][j - 1] + 1;
b[i][j] = 1;
}
else
if (c[i][j - 1] > c[i - 1][j])
{
c[i][j] = c[i][j - 1];
b[i][j] = 2;
}
else
{
c[i][j] = c[i - 1][j];
b[i][j] = 3;
}
}
}
return c[m][n];
}
void LCS(int i, int j, char *x, int **b)
{
if (i == 0 || j == 0)
return;
if (b[i][j] == 1)
{
LCS(i - 1, j - 1, x, b);
cout << x[i-1] << " ";
}
else
if (b[i][j] == 2)LCS(i - 1, j, x, b);
else
LCS(i, j - 1, x, b);
}
int main()
{
int charNum_1;
int charNum_2;
cout << "请输入两个字符串的长度:" << endl;
cin >> charNum_1 >> charNum_2;
char *x = new char[charNum_1];
char *y=new char[charNum_2];
int **b = new int*[charNum_1];
int **c = new int*[charNum_1];
for (int i = 0;i <= 7;i++)//申请空间
{
b[i] = new int[charNum_2];
c[i] = new int[charNum_2];
}
for (int i = 0;i <= charNum_1;i++)//初始化
{
for (int j = 0;j <= charNum_2;j++)
{
b[i][j] = 0;
c[i][j] = 0;
}
cout << endl;
}
cout << "请输入字符串 1:";
for (int i = 0;i <charNum_1;i++)
{
cin >> x[i];
}
cout << "请输入字符串 2:";
for (int i = 0;i <charNum_2;i++)
{
cin >> y[i];
}
int len=LengthString(x, y, charNum_1, charNum_2, c, b);
cout << "最长公共子串长度是:" << len << endl;
cout << "最长公共子串是:";
LCS(charNum_1, charNum_2, x, b);
for (int i = 0; i <= charNum_1; i++) //释放动态申请的二维数组空间
delete[] c[i];
delete[] c;
}
由于每个数组单元计算耗费的时间,所以算法LengthString的时间复杂度为
2.4 求解最长序列输出
由以上我们求出的只是最长公共子序列的长度,最长子序列是什么还没求出。所以我们根据数组继续构造最长子序列,首先从开始,在数组中依次搜索,当时,表示和的最长公共子序列是由和加上得到的,当表示和的最长公共子序列和与的最长公共子序列相同,当时,表示和的最长公共子序列和与的子序列相同,由此我们得到递归求解公共子序列的算法。
void LCS(int i, int j, char *x, int **b)
{
//i和j分别表示序列x和y的长度
if (i == 0 || j == 0)
return;
if (b[i][j] == 1)
{
LCS(i - 1, j - 1, x, b);
cout << x[i-1] << " ";
}
else
if (b[i][j] == 2)LCS(i - 1, j, x, b);
else
LCS(i, j - 1, x, b);
}
在算法中,由于每次递归使得i和j的值每次都减小1,所以算法的时间复杂度为.