最长公共子序列(LCS)
定义:
最长公共子序列,英文缩写为LCS(Longest Common Subsequence)。其定义是,一个序列 S ,如果分别是两个或多个已知序列的子序列,且是所有符合此条件序列中最长的,则 S 称为已知序列的最长公共子序列。而最长公共子串(要求连续)和最长公共子序列是不同的
例如 下面两个单词中颜色标记出来的 data 就是 didactical 和 advantage 的 最长公共子序列, 长度为4;
didactical
advantage
求解:
一、首先我们用递归思想设计一个正确、可行的解。
①如果其中一个序列为空,那么最长公共子序列一定为空,长度为 0 。
②我们比较这两个序列的最后一个字母,如果相同,我们就可以将可以将问题规模缩小1位。
例如:
apple appl
abstpdple 最后一个相同 , 我们就可以进而求 abstpdpl 的最长公共子序列, 当然这时候要记录下已经有了的公共部分。
③如果最后一位不同怎么办?
例如
ap
abstpd , 可以想到 ,这时候问题分为了两个部分:
a ap
求解 abstpd 的最长公共子序列 ,和 abstpd 的最长公共子序列
因为最后一个字符不一样,所以我们必定要舍弃两个序列中其中一个的最后一位。显然,我们需要这两个问题中公共子序列更长的那一个。
这样,整个递归算法就算描述完了。
实现的代码在文章最后给出,最好自己直接按照上述描述敲出来代码。
二、寻找优化:动态规划的思想。
我们可以简单的对上面递归的算法进行一些时间分析
最好的情况,也就是不出现③中情况的时候,只需要O(min(lena,lenb)).
然而一旦出现③,那么问题就会分解为两个问题,更糟糕的是,这两个问题的子问题可能雷同!
如图可以看到,两个子问题的子问题可能雷同,将造成大量的重复计算。
最坏的情况将达到 C(lena,lena+lenb)(组合数符号), 当lena == lenb 时 ,大致为 O(2^n)。
时间复杂度达到了指数级别!
这里我们运用动态规划的思想,刚才递归是自顶向下,我们自底向上的去求解问题。
首先初始化dp数组的第一行和第一列为0;
如图所示,我们可以一行一行的填写这张表,运用上面递归中所讲的方法,
如果遇到相同的字符,dp[i][j] = dp[i-1][j-1] + 1 ,
如果遇到不同的字符 dp[i][j] =max(dp[i-1][j] , dp[i][j-1] ) .
这样每个空格只用遍历一次就能求出所有的结果,dp[lena][lenb] 就是结果啦。
源代码:
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> using namespace std; const int maxn = 600; int dp[maxn][maxn]; //递归版本的LCS int fun(string a,string b,int count){ int lena = a.length(); int lenb = b.length(); if(lena == 0 || lenb == 0) return count; string tmpa,tmpb; tmpa = a.substr(0,lena-1); tmpb = b.substr(0,lenb-1); if(a[lena-1] == b[lenb-1]){ return fun(tmpa,tmpb,count+1); } else { return max(fun(tmpa,b,count),fun(a,tmpb,count)); } } //动态规划LCS void dynamic(string a,string b){ int lena = a.length(); int lenb = b.length(); for(int i=0;i<=lena;i++){ dp[i][0] = 0; } for(int i=0;i<=lenb;i++){ dp[0][i] = 0; } for(int i=0;i<lena;i++){ for(int j=0;j<lenb;j++){ if(a[i] == b[j]){ dp[i+1][j+1] = dp[i][j] + 1; } else{ dp[i+1][j+1] = max(dp[i+1][j] , dp[i][j+1]); } } } printf("%d\n",dp[lena][lenb]); } void print(){//打印dp数组 for(int i=0;i<=lena;i++){ for(int j=0;j<=lenb;j++){ printf("%d ",dp[i][j]); } puts(""); } } int main(){ string a,b; while(getline(cin,a)){ getline(cin,b); dynamic(a,b); //动态规划 // int ans = fun(a,b,0); //递归版本 // printf("%d\n",ans); } return 0; }