动态规划经典问题----最长公共子序列

给定两个序列 X={x1,x2,x3,…,xm}和 Y={y1,y2,y3,…,yn},找出 X 和 Y 的一个
最长的公共子序列。
例如:X=(A,B,C,B,A,D,B),Y=(B,C,B,A,A,C),那么最长公共子

序列是 B,C,B,A;

分析:由于如果直接暴力破解,时间复杂度是我们避之不及的爆炸性指数。可以用动态规划分析其最优子结构,然后建立最优值的递归式,有点类似于我们中学所学数列中的通项,但还是有点区别。

(一)假设:已知Zk={z1,z2,z3.......zk}是X={x1,x2,x3,…,xm}和 Y={y1,y2,y3,…,yn}的最优子结构

①:当Xm=Yn=Zk时,Z={z1,z2.....zk-1}时,Zk-1是Xm-1和 Yn-1的最长公共子序列。

反证法:如果Zk-1不是Xm-1和 Yn-1的最长公共子序列。则存在一个序列M为Xm-1和 Yn-1的最长公共子序列,则M>Zk-1,那么在三序列后面加上Xm=Yn=Zk,因为M+Zk>Zk-1+Zk,则M+Zk为Xm和 Yn的最长公共子序列,这与假设冲突,所以Zk-1就是Xm-1和 Yn-1的最长公共子序列。

②当Xm≠Yn,Xm≠Zk时,我们可以先把Xm去掉,则Zk是Xm-1和Yn的最长公共子序列。

反证法:如果Zk不是Xm-1和Yn的最长公共子序列。则存在一个序列M是Xm-1和Yn的最长公共子序列,所以M>Z,当把Xm加到Xm-1处时,由于M>Z,则M是Xm和Yn的最长公共子序列。这与假设冲突。

③当Xm≠Yn,Yn≠Zk时,同②证法一样,Zk是Xm和Yn-1的最长公共子序列。

这说明这题具有最优子结构的性质,是典型的D题。

(二) 建立最优值递归式

用C[i][j]来存储最优值

①:当Xm=Yn=Zk时,C[i][j]=C[i1][j-1]+1;

②:当Xm≠Yn时,我们只需要比较Xm-1和Yn与Xm和Yn-1的最长公共子序列哪个更长;即:C[i][j]=max{C[i-1][j],C[i][j-1]};

当i=j=0时,C[i][j]=0;

(三)向上计算最优解

由于最优值只是得到了一个最长的一个数值,并不知道序列是什么,假设猜C[m][n]=5,我们知道这个最长公共子序列长度是5,那么这个5是怎样得到的?我们可以通过倒向追踪法知道5是从哪里来的。根据递归式:

当Xi=Yj时,C[i][j]=C[i-1][j-1]+1;

当Xi≠Yj时:C[i][j]=max{C[i-1][j],C[i][j-1]};

则C[i][j]有三个来源:C[i-1][j-1]+1和C[i-1][j]和C[i][j-1];

在建立最优值时可以用一个辅助数组W[i][j]来记录这三个来源:

C[i][j]=C[i-1][j-1]+1;W[i][j]=1;

C[i][j]=C[i][j-1];W[i][j]=2;

C[i][j]=C[i-1][j];   W[i][j]=3;

这样就可以根据W[m][n]的值来倒向追踪,当W[i][j]=1时;输出Xi,当W[i][j]=2时;追踪C[i][j-1];当W[i][j]=3时;追踪C[i-1][j];直到i=0或者j=0;

代码细节:

#include<iostream>
#include<string.h>
using namespace std;
#define N 1002
int C[N][N], b[N][N];
char s1[N], s2[N];
int len1, len2;
void LCSL()
{
	for(int i=1;i<=len1;i++)
		for (int j = 1; j <=len2; j++)
		{
			if (s1[i-1] == s2[j-1])
			{
				//如果当前字符串相同,则其公共子序列的长度为该字符串前的最长公共序列+1
				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;
				}
			}
		}
}
//倒向追踪每一步的结果
void print(int i, int j)
{
	if (i == 0 || j == 0)
		return;
	if (b[i][j] == 1)//说明s1[i-1]==s2[j-1]
	{
		print(i - 1, j - 1);
		cout << s1[i - 1];
		
	}
	else
		if (b[i][j] == 2)//由上面程序可知此时s1[i-1]≠s2[j-1]且最优解来源于C[i][j]=C[i][j-1]
	{
		print(i, j - 1);//所以递归回源点
	}
		else 
		   print(i - 1, j);
	
}
int main()
{
		cin >> s1;
	cin >> s2;
    //字符串的长度
	len1 = strlen(s1);
	len2 = strlen(s2);
	for (int i= 0; i <= len1; i++)
	{
		C[i][0] = 0;//初始化第一列为 0 
	}
	for (int j = 0; j <= len2; j++)
	{
		C[0][j] = 0;//初始化第一行为 0 
	}
	LCSL(); //求解最长公共子序列
	cout << "s1 和 s2 的最长公共子序列长度是:" << C[len1][len2] << endl;
	cout << "s1 和 s2 的最长公共子序列是:";
	print(len1, len2); //递归构造最长公共子序列最优解
	return 0;
}







猜你喜欢

转载自blog.csdn.net/weixin_41676901/article/details/80545501