最长公共子序列(C语言)

问题1:钢条切割
问题2:矩阵链乘法

《算法导论》中动态规划的第三个问题是最大子序列
这里的最大子序列的定义是:找出一个序列S3,S3在S1和S2中都出现,且出现的顺序相同,现在找出最长的一个S3
对于序列A=“ABCBDAB”,B=“BDCABA”
他的一个最大子序列是“BCBA”。
对于一个序列X=<x1,x2,…xm>,序列Y=<y1,y2…yn>,他们的一个最大子序列Z=<z1,z2,zk>。可以得到如下三个结论

  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的一个最大子序列

通过上面三个例子,我们可以推出X,Y最大子序列的递归写法。
想要知道子序列c[i][j]的长度要经过对不同情况讨论来判断,如若xi=yj 则c[i][j]的长度为c[i-1][j-1]+1,xi,yj添加前一个最大子序列的至末尾。如果xi!=yj则序列长度为c[i-1][j]和c[i][j-1]中较大的那一个。这里增加了对子问题情况的判断,针对不同的情况来选择不同的子问题
动态规划代码:

	for (i = 1; i <= n; i++)
	{
		for (j = 1; j <= m; j++)
		{
			if (A[i-1] == B[j-1])
			{
				len[i][j] = len[i - 1][j - 1] + 1;
				dis[i][j] = 1;
			}
			else if (len[i - 1][j] >= len[i][j - 1])
			{
				len[i][j] = len[i - 1][j];
				dis[i][j] = 3;
			}
			else
			{
				len[i][j] = len[i][j - 1];
				dis[i][j] = 2;
			}
		}
	}

另外创建数组dis[][]来储存每一次的信息,如果xi=yj则为1,另外两种情况为dis[][]中储存为2,3;
输出最长子序列的代码:

void show(int i, int j)
{
	if (i == 0 || j == 0)
		return;
	if (dis[i][j] == 1)
	{
		show(i - 1, j - 1);
		printf("%c ", A[i-1]);
	}
	else if (dis[i][j] == 3)
	{
		show(i - 1, j);
	}
	else
	{
		show(i, j - 1);
	}
}

这里可以这样理解,若dis[i][j]=3,代表的是len[i-1][j]>len[i][j-1]所以传给show的参数为(i-1,j),向着子序列更长的递归
完整代码:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
int len[100][100];
int dis[100][100]; //1代表两个都是,2代表行是左,3代表列是上
char A[100] = "ABCBDAB";
char B[100] = "BDCABA";
void show(int, int);
int main()
{
	int n = strlen(A);
	int m = strlen(B);
	printf("%d  %d\n", n, m);
	int i, j, k;
	for (i = 0; i <= n; i++)
		len[i][0] = 0;
	for (i = 0; i <= m; i++)
		len[0][i] = 0;
	for (i = 1; i <= n; i++)
	{
		for (j = 1; j <= m; j++)
		{
			if (A[i-1] == B[j-1])
			{
				len[i][j] = len[i - 1][j - 1] + 1;
				dis[i][j] = 1;
			}
			else if (len[i - 1][j] >= len[i][j - 1])
			{
				len[i][j] = len[i - 1][j];
				dis[i][j] = 3;
			}
			else
			{
				len[i][j] = len[i][j - 1];
				dis[i][j] = 2;
			}
		}
	}
	printf("最大子序列长度:%d  ", len[n][m]);
	for (i = 0; i <= n; i++)
	{
		for (j = 0; j <= m; j++)
		{
			printf("%d ", dis[i][j]);
		}
		puts("");
	}
	puts("子序列为:");
	show(n, m);
}
void show(int i, int j)
{
	if (i == 0 || j == 0)
		return;
	if (dis[i][j] == 1)
	{
		show(i - 1, j - 1);
		printf("%c ", A[i-1]);
	}
	else if (dis[i][j] == 3)
	{
		show(i - 1, j);
	}
	else
	{
		show(i, j - 1);
	}
}

测试结果:
在这里插入图片描述

总结

有人认为动态规划的名字十分具有迷惑性,他可以等同于记忆化搜索。可见动态规划中储存的作用十分的重要,储存可以避免子问题的重复计算,大大地升程序运行的速度。

  • 0
    点赞
  • 0
    评论
  • 1
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 1024 设计师:白松林 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值