最长公共子序列(动态规划)

目录

 

1 子序列概念

2  问题描述

2.1 问题分析:

2.2 动态规划求解公式

2.3 算法展示

2.4 求解最长序列输出


1 子序列概念

一个给定序列的子序列是在序列中删除若干个元素后得到的序列。在这里,首先说明子序列的概念(切记子序列非子集的概念),例如X={B,C,D,B}是序列Y={A,B,C,B,D,A,B}的一个子序列,则序列X在序列Y中相对应的下标为{2,3,5,7},序列XY的下标都是从1开始。

2  问题描述

如果给定两个序列X和序列Y,当另外一个序列Z即是X的子序列又是Y的子序列,那么称序列Z是序列X和序列Y的公共子序列。

假如X=A,B,C,B,D,A,B,Y=B,D,C,A,B,A这两个序列,则序列Z=B,C,AXY的一个公共子序列,但他不是一个最长的公共子序列。而序列{B,C,B,A}才是一个最长的公共子序列,他的长度为4,应为序列XY没有长度大于四的公共子序列。

了解了最长公共子序列,那么最长公共子序列问题就是在给定的两个序列XY中,找出他们最长的公共子序列这样一个问题。

2.1 问题分析:

设序列X={x_{1},x_{2}......x_{m}}和序列Y={y_{1},y_{2}.....y_{n}}的最长公共子序列是Z={z_{1},z_{2}......z_{k}}

(1)如果x_{m}=y_{n},则z_{k}=x_{m}=y_{n},并且Z_{k-1}X_{m-1}Y_{n-1}的最长公共子序列。

(2)如果x_{m}\neq y_{n}并且z_{k}\neq x_{m},那么ZX_{m-1}Y的一个最长公共子序列。

(3)如果x_{m}\neq y_{n}并且z_{k}\neq y_{n},那么ZXY_{n-1}的一个最长公共子序列。

在这里,其中X_{m-1}={x_{1},x_{2}......x_{m-1}};Y_{n-1}={y_{1},y_{2}.....y_{n-1}};Z_{k-1}={z_{1},z_{2}......z_{k-1}}.

2.2 动态规划求解公式

根据以上问题分析,我们要找出序列XY的最长公共子序列,可以按照以下递归进行求解:当x_{m}=y_{n}时,(即两个序列最后一个字符相同),那我们的变为求解X_{m-1}Y_{n-1}得最长公共子序列在加上x_{m}即可,就可以得到XY的一个最长公共子序列。当x_{m}\neq y_{n}时,必须求解两个问题,即找出X_{m-1}YXY_{n-1}两者当中较长的一个公共子序列 ,即得到最终结果,所以我们建立了以上的递归公式求解。我们用c[i][j]记录序列XY的最长公共序列的长度,其中当i=0或者j=0时c[i][j]=0表示此时最长公共序列为。b[i][j]记录c[i][j]的值是由哪一个子问题求解得到的。XY的最长公共子序列记录在c[i][j]

下面我们就可以写出求最长公共子序列的递推公式:

c[i][j]=\left\{\begin{matrix} 0 & & & & & & & i=0||j=0& & \\ c[i-1][j-1]+1& & & & & & & i,j>0;x_{i}=y_{j}& & \\ max(c[i][j-1],c[i-1][j])& & & & & & & i,j>0;x_{i}\neq y_{j} & & \end{matrix}\right.

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;
	
}

 由于每个数组单元计算耗费O(1)的时间,所以算法LengthString的时间复杂度为O(n*m)

2.4 求解最长序列输出

 由以上我们求出的只是最长公共子序列的长度,最长子序列是什么还没求出。所以我们根据b数组继续构造最长子序列,首先从b[i][j]开始,在数组中依次搜索,当b[i][j]\doteq 1时,表示XY的最长公共子序列是由X_{m-1}Y_{n-1}加上x_{m}得到的,当b[i][j]\doteq 2表示XY的最长公共子序列和X_{i-1}Y_{j}的最长公共子序列相同,当b[i][j]\doteq 3时,表示XY的最长公共子序列和X_{i}Y_{j-1}的子序列相同,由此我们得到递归求解公共子序列的算法。

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);
}

LCS算法中,由于每次递归使得i和j的值每次都减小1,所以算法的时间复杂度为O(m+n).

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章