《算法导论》中动态规划的第三个问题是最大子序列
这里的最大子序列的定义是:找出一个序列S3,S3在S1和S2中都出现,且出现的顺序相同,现在找出最长的一个S3。
对于序列A=“ABCBDAB”,B=“BDCABA”
他的一个最大子序列是“BCBA”。
对于一个序列X=<x1,x2,…xm>,序列Y=<y1,y2…yn>,他们的一个最大子序列Z=<z1,z2,zk>。可以得到如下三个结论
- 如果xm=yn,那么有zk=xm=yn。且zk-1是xm-1与yn-1的一个最大子序列
- 如果xm!=yn,那么zk !=xm,z是xm-1与y的一个最大子序列
- 如果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);
}
}
测试结果:
总结
有人认为动态规划的名字十分具有迷惑性,他可以等同于记忆化搜索。可见动态规划中储存的作用十分的重要,储存可以避免子问题的重复计算,大大地升程序运行的速度。