【重征算法路】之六:最长公共子序列算法 (开始进行动态规划的学习)

【重征算法路】之六:最长公共子序列算法 (开始进行动态规划的学习)

5天没看算法了,赶紧把自己拉回来学习、写博客!

解释:分治法、动态规划的区别

1,分治法的基本思想
就拿典型的分治法思想的斐波那契数列来举例,代码如下:

int fibonacci(int n){
	if(n<=1) return 1;
	return fibonacci(n-1)+fibonacci(n-2);
}

斐波那契数列大概的运行导图是这样的:
在这里插入图片描述
即:当分治法解决问题时,一般会采用递归的方式,把次问题分解成两个子问题,然后每个子问题再往下分,一直分到该子问题不可再分为止。
2,动态规划的基本思想
我们继续拿上面的斐波那契数列举例。

分治法计算fibonacci(5)的时候,我们发现:fibonacci(3)计算了2次,fibonacci(2)计算3次,fibonacci(1)计算5次,fibonacci(0)计算3次。
假设每次计算都要消耗一定时间的话,那么重复的计算使得分治法在执行效率上并不高。

如果以动态规划的思想来解斐波那契数列,那么首先会计算fibonacci(0)—fibonacci(5)的值,然后存入表中,然后计算每个fibonacci(n)会用到多少次,最后相加,当调用fibonacci(n)的时候,动态规划的思想是去查表(以往存入值的数组),而不是再次计算。

3,分治法与动态规划的区别
区别在于: 分治法会造成大量的重复计算,而动态规划会把数据存入表中,每次需要计算的时候直接去查表,这样会节省资源空间、提高算法效率。
限制在于 比如斐波那契数列,fibonacci(5)这样的数据的值,是需要一步步推导的,并不能直接算出来,所以这种问题其实还是分治法解决比较简单。

算法介绍

1,算法的问题描述:
若序列X={A,B,C,B,D,A,B},Y={B,D,C,A,B,A},则序列{A,B,C}是序列X的一个子序列,序列{B,C,B,A}是序列X,Y的最长公共子序列。现有X={x1,x2,x3 …… xm},Y={y1,y2,y3 …… yn},找出X,Y的最长公共子序列Z={z1,z2,z3 …… zk}。
2,采用动态规划的算法需要满足两个条件:最优子结构性质子问题的递归结构

最长公共子序列的最优子结构性质:
(1)若xm=yn,则zk=xm=yn,且Zk-1是Xm-1和Yn-1的最长公共子序列。
(2)若xm!=yn且zk!=xm,则Z是Xm-1和Y的最长公共子序列。
(3)若xm!=yn且zk!=yn,则Z是X和Yn-1的最长公共子序列。

子问题的递归结构:
用二维数组c[i][j]来记录序列Xi和Yi的最长公共子序列的长度。
当i=0或j=0时,空序列是Xi或Yi的最长序列,所以此时c[i][j]=0;
其他情况下,由最优子结构性质得递归关系如下:

在这里插入图片描述

算法时间复杂度

该算法有两个函数LCSLength和LCS,LCSLength的时间复杂度为O(mn),LCS的时间复杂度为O(m+n)。

算法代码·C++版

void LCSLength(int m,int n,char *x,char *y,int c[][10],int b[][10]){
	int i,j;
	for(i=1;i<=m;i++) c[i][0]=0;
	for(i=1;i<=n;i++) c[0][i]=0;
	
	for(i=1;i<=m;i++)
	for(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-1][j]>c[i][j-1])
			{
				c[i][j]=c[i-1][j];
				b[i][j]=2;
			}
		else
			{
				c[i][j]=c[i][j-1];
				b[i][j]=3;
			}
	}
}

void LCS(int i,int j,char *x,int b[][10]){
	if(i==0||j==0) return;
	if(b[i][j]==1) {LCS(i-1,j-1,x,b);cout<<x[i];}
	else if(b[i][j]==2) LCS(i-1,j,x,b);
	else LCS(i,j-1,x,b);
}

算法解释

API解释:

该算法的API为:LCSLength(int m,int n,char x,char y,int c,int b)
m 是X字符数组中字符的长度
n 是Y字符数组中字符的长度
x 是X字符数组
y 是Y字符数组
c 是存储Xi和Yi的最长公共子序列的长度的二维数组
b 是记录c中的值是由哪一个子问题的解得到的二维数组
该API无返回值,只是会把二维数组c、b存入数据
该算法的第二API为:LCSLCS(int i,int j,char x,int b)
i 是X字符数组中字符的长度
j 是Y字符数组中字符的长度
x 是X字符数组
b 是记录c中的值是由哪一个子问题的解得到的二维数组
该 API无返回值,但在运行期间会依次输出最长公共子序列
【注:】 字符数组X、Y存放的时候,应该从X[1]开始存放。例如x[8]={’ ',‘a’,‘b’,‘c’,‘d’,‘b’,‘a’,‘c’},其m值为7。Y也同样。

运行解释:
首先,main函数为

	int main() {
	int m=7,n=6;
	int c[10][10]={0},b[10][10]={0};
	char x[8]={' ','a','b','c','b','d','a','b'};
	char y[7]={' ','b','d','c','a','b','a'};
	LCSLength(m,n,x,y,c,b);
	LCS(m,n,x,b);
	return 0;
}

当运行LCSLength(m,n,x,y,c,b)时,程序运行:

	for(i=1;i<=m;i++) c[i][0]=0;
	for(i=1;i<=n;i++) c[0][i]=0;

1,以上语句是为含空的字符串赋0;c[i][j]是存储Xi与Yi的最长公共子序列长度,所以当i或者j为0,即X或Y有一方为空的话,最长公共子序列都为0。如下图:
在这里插入图片描述

for(i=1;i<=m;i++)
	for(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-1][j]>c[i][j-1])
			{
				c[i][j]=c[i-1][j];
				b[i][j]=2;
			}
		else
			{
				c[i][j]=c[i][j-1];
				b[i][j]=3;
			}
	}

2,以上语句是,先取X字符数组中的第一位与Y中的所有依次比较。然后是X中的第二位、第三位……
c[i][j]数组运行大概是这样:(请对照着代码草稿纸上写一遍,很简单的)
在这里插入图片描述
在这里插入图片描述

3,同理,得出b数组为:
在这里插入图片描述

4,最后LCS算法进行对字符串的输出。LCS算法采用递归,虽然是i,j从大到小寻找值,但是因为递归LCS()在cout前面,所以会倒序输出。

发布了57 篇原创文章 · 获赞 43 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_43592352/article/details/100666603