1:基本概念
子序列(Subsequence):在不改变原字符串元素的先后位置
公共子序列:两个字符串共有的子序列
再介绍一个相似的概念
子串(Substring):元素连续的子序列
下面举个例子:
现在有两个字符串
A:abcdefg
B:appleorange
则
abc:A的子序列,A的子串
ade:A的子序列
sad:什么都不是
apple:B的子序列,B的子串
ae:A和B的公共子序列
2:功能实现
2.1:暴力查找法 O(2^(n+m))
……
……
……
……
……
……
长度为n的字符串就有2的n次方个子序列,暴力查找不现实,这里也不贴代码了。2.2:动态规划法 O(n*m)
特征分析:动态规划的特点就是当前状态可以通过之前的状态确定,且可以得出状态转移方程。
所以解决动态规划问题最重要的步骤就是分析特征。
先丢出转移方程
dp[i,j]表示匹配到a[i-1]和b[j-1]时最长公共子序列长度
(为了书写简便,dp[i,j]一般对应着a[i-1],b[i-1])
{0 若i==0或j==0
dp[i,j]={dp[i-1,j-1]+1 若i,j>0且a[i-1]==b[j-1]
{max(dp[i-1,j],dp[i,j-1] 若i,j>0且a[i-1]!=b[j-1]
再来分析
如果 i==0或j==0 的话 只有一个字符串,则最长公共子序列长度为0 dp[i,j]=0
如果 i,j>0且a[i]=b[j] 当前字符相同,最长公共子序列长度应+1,dp[i,j]=dp[i-1,j-1]+1
如果 i,j>0且a[i]!=b[j]当前字符不同,最长公共子序列长度不变,dp[i,j]=max(dp[i-1,j],dp[i][j-1])
来个例子,首先我们要有两个字符串,duang!
A:abcdefabcde
B:azddbcabezz
abcdefabcde
azddbcabezz
然后上个图!
已初始化0
然后填充第一行
再处理第二行
一口气完成所有行
加个颜色区分一下
是不是一下就很直观了呢
贴个代码
void LCS_1(char *c,char *d){
int dp[MAXX][MAXX];
int len1,len2,i,j,k;
len1=strlen(c);
len2=strlen(d);
for(i=0;i<=len1;i++){
for(j=0;j<=len2;j++){
if(i==0||j==0) dp[i][j]=0;
else if(c[i-1]==d[j-1])dp[i][j]=dp[i-1][j-1]+1;
else if(c[i-1]!=d[j-1])dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
}
}
printf("%d\n",dp[len1][len2]);
LCS_SUB(c,d,dp);
}
void LCS_2(char *c,char *d){//滚动数组法
int dp[2][MAXX];
int len1,len2,i,j,k;
len1=strlen(c);
len2=strlen(d);
for(i=0;i<=len1;i++){
for(j=0;j<=len2;j++){
if(i==0||j==0) dp[1][j]=0;
else if(c[i-1]==d[j-1])dp[1][j]=dp[0][j-1]+1;
else if(c[i-1]!=d[j-1])dp[1][j]=max(dp[0][j],dp[1][j-1]);
}
for(j=0;j<len2;j++){
dp[0][j]=dp[1][j];
}
}
printf("%d\n",dp[1][len2]);
}
3:输出公共子序列
首先对上面的图进行一下加工我们可以清楚的看到一条回溯路径,
这就是我们要的子序列了。
然后从右下往左上查找,再逆序输出即可
void LCS_SUB(char *c,char *d,int dp[][MAXX]){
int i,j,k;
i=strlen(c);
j=strlen(d);
char e[MAXX];
int len=0;
while(i!=0&&j!=0){
if(dp[i-1][j]==dp[i][j-1]&&dp[i-1][j]==dp[i-1][j-1]&&dp[i-1][j]<dp[i][j]){
e[len]=c[i-1];
len++;
i--;
j--;
}
else if(dp[i][j-1]==dp[i][j]){
j--;
}
else if(dp[i-1][j]==dp[i][j]){
i--;
}
}
for(i=len-1;i>=0;i--){
printf("%c",e[i]);
}
printf("\n");
}