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

最长公共子序列-动态规划(CC++),第1张

最长公共子序列-动态规划(C/C++) 动态规划简述

使用最优子结构特性,动态规划算法采用自底向上的方式计算,在求解的过程中保存已经计算好的子问题的最优解,当子问题的最优解被重复使用时,无需再次计算直接从保存的空间中调用。

举个例子: 斐波那契数列

        后一项等于前两项累加之和,如果采用递归则一直要从n递归到0。

        如果采用动态规划则计算前两项保存到数组之中,后面每一项都直接调用前两项之和累加并保存到数组之中供后面的元素调用,便不用占用计算时间。

最长公共子序列问题描述

 

 下图给出了最优解的递推关系

        利用递推关系来生成最大子序列矩阵,再生成最大子序列矩阵的同时,生成一个回溯矩阵,用来回溯最大公共子序列都有哪些。

具体的算法思想

 需要定义一下矩阵

int c[8][8]; //c计算并存储最大子序列 
int s[8][8];	//s为回溯寻找子序列元素 1左上找并且为子序列元素 2上回溯 3左回溯
c[][]矩阵的生成

c[7][7]存储最大子序列个数,s存储回溯矩阵。

输入的公共序列为X:0abcbdab Y:0bdcaba

根据递推关系式,c矩阵第0行第0列都初始化为0;

当X第i个元素与Y的第j个元素相等时,c[i][j] = c[i-1][j-1] +1 也就是左上角的元素+1;

当X第i个元素与Y的第j个元素不相等时,这时我们要判断是上面的元素比左边的元素大,还是左边的元素比上面的元素大。

当c左边的元素大于等于上面的元素时,c[i][j] = c[i - 1][j],也就是等于左边的元素;

当c左边的元素小于上面的元素时,c[i][j] = c[i][j - 1];,也就是等于上面的元素;

这样计算完成的矩阵c的最后一个元素也就是最大公共子序列的个数。

s[][]矩阵的生成

s矩阵生成也分为三种情况,分别对应上面的递推关系的三种情况

1. 当X第i个元素与Y的第j个元素相等时,将s[i][j] = 1

2. 当c左边的元素大于等于上面的元素时,将s[i][j] = 2

3. 当c左边的元素小于上面的元素时,将s[i][j] = 3;

代码实现

这里我将生成c与s的代码直接放入主函数之中了,文章结尾给出全部代码

        根据上面c[i][j]的递推关系,可以计算出矩阵c[][],在这里我们要注意,计算矩阵c之前需要初始化第0列和第0行,为了防止越界,实际上输入两个子序列时要在子序列之前加上‘0’,然后从子序列第1个元素开始判断。我们判断公共子序列也是从第一行开始判断防止上来c[i][j]出现递推关系中的第二种或者第三种情况。所以初始化0 行0列为0。

这里我将两个矩阵全部初始化为0增加了时间复杂度。

for (decltype(X.length()) i = 0; i < X.length(); i++) {
		for (decltype(X.length()) j = 0; j < Y.length(); j++) {
			c[i][j] = 0;
			s[i][j] = 0;
		}
	}

s与c矩阵的生成代码

for (decltype(X.length()) i = 1; i < X.length(); i++)
	{
		for (decltype(Y.length()) j = 1; j < Y.length(); j++) {
			if (X[i] == Y[j] )
			{
				c[i][j] = c[i - 1][j - 1] + 1;	//相等 等于左上角元素
				s[i][j] = 1;			//回溯为左上回 且为子序列中元素
			}
			else if (c[i - 1][j] >= c[i][j - 1]) {	//不相等 且左边大于上面   等于左边元素
				c[i][j] = c[i - 1][j];	
				s[i][j] = 2;			//回溯为左回
			}
			else {
				c[i][j] = c[i][j - 1];	// 否则不相等且左边小于上面 等于上面元素
				s[i][j] = 3;			//回溯为上回
			}
		}
	}

 回溯CLCS代码如下

void CLCS(int i,int j) {
	if (i==0||j==0)
	{
		return;
	}
	if (s[i][j] == 1)
	{
		CLCS(i - 1, j - 1);
		cout << X[i] << " ";
	}
	else if (s[i][j] == 2) {
		CLCS(i - 1, j);
	}
	else
	{
		CLCS(i, j - 1);
	}
}

 输入的两个序列分别为:

0abcbdab
0bdcaba

使用文件重定向生成的输出文件为:

整体代码 
void CLCS(int i, int j);

int c[8][8]; //c计算并存储最大子序列 
int s[8][8];	//s为回溯寻找子序列元素 1左上找并且为子序列元素 2上回溯 3左回溯
string X, Y;

int main() {
	cin >> X >> Y;// X = 8, Y = 7

	for (decltype(X.length()) i = 0; i < X.length(); i++) {
		for (decltype(X.length()) j = 0; j < Y.length(); j++) {
			c[i][j] = 0;
			s[i][j] = 0;
		}
	}


	for (decltype(X.length()) i = 1; i < X.length(); i++)
	{
		for (decltype(Y.length()) j = 1; j < Y.length(); j++) {
			if (X[i] == Y[j] )
			{
				c[i][j] = c[i - 1][j - 1] + 1;	//相等 等于左上角元素
				s[i][j] = 1;			//回溯为左上回 且为子序列中元素
			}
			else if (c[i - 1][j] >= c[i][j - 1]) {	//不相等 且左边大于上面   等于左边元素
				c[i][j] = c[i - 1][j];	
				s[i][j] = 2;			//回溯为左回
			}
			else {
				c[i][j] = c[i][j - 1];	// 否则不相等且左边小于上面 等于上面元素
				s[i][j] = 3;			//回溯为上回
			}
		}
	}

	cout << "最大子序列矩阵:" << endl;
	for (decltype(X.length()) i = 0; i < X.length(); i++) {
		for (decltype(X.length()) j = 0; j < Y.length(); j++) {
			cout << c[i][j]<<" ";
		}
		cout << endl;
	}

	cout << "回溯矩阵:" << endl;
	for (decltype(X.length()) i = 0; i < X.length(); i++) {
		for (decltype(X.length()) j = 0; j < Y.length(); j++) {
			cout << s[i][j] << " ";
		}
		cout << endl;
	}

	CLCS( X.length() - 1, Y.length() - 1);

	system("pause");
	return 0;
}

void CLCS(int i,int j) {
	if (i==0||j==0)
	{
		return;
	}
	if (s[i][j] == 1)
	{
		CLCS(i - 1, j - 1);
		cout << X[i] << " ";
	}
	else if (s[i][j] == 2) {
		CLCS(i - 1, j);
	}
	else
	{
		CLCS(i, j - 1);
	}
}

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/zaji/5699381.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存