您的位置:首页 > 编程语言

常见编程——最长公共子序列(不连续)和最长公共子串(连续)

2017-09-19 08:47 253 查看
一个给定的序列的子序列,就是将给定序列中零个或多个元素去掉之后得到的结果。给定串中任意个连续的字符组成的子序列称为该串的子串。



一、求解LCS问题,不能使用暴力搜索方法。一个长度为n的序列拥有
2的n次方个子序列,它的时间复杂度是指数阶,太恐怖了。解决LCS问题,可以找出推导公式用递归方法,也可以借助动态规划的思想。

题目:如果字符串一的所有字符按其在字符串中的顺序出现在另外一个字符串二中,则字符串一称之为字符串二的子串。

注意,并不要求子串(字符串一)的字符必须连续出现在字符串二中。请编写一个函数,输入两个字符串,求它们的最长公共子串,并打印出最长公共子串(不连续)。

例如:输入两个字符串BDCABA和ABCBDAB,字符串BCBA和BDAB都是是它们的最长公共子序列,则输出它们的长度4,并打印任意一个子序列。

(1)递归方法求最长公共子序列的长度

1)设有字符串a[0...n],b[0...m],下面就是递推公式。

                  


当数组a和b对应位置字符相同时,则直接求解下一个位置;当不同时取两种情况中的较大数值。

#include<iostream>
#include<string>
#include<algorithm>
using namespace std;
int LCS(string s1,string s2,int i,int j){
if(i>=s1.length()||j>=s2.length()) return 0;
if(s1[i]==s2[j]) return LCS(s1,s2,i+1,j+1)+1;
else return LCS(s1,s2,i+1,j)>LCS(s1,s2,i,j+1)?LCS(s1,s2,i+1,j):LCS(s1,s2,i,j+1);
}
int main(){
string s1,s2;
cin >> s1>>s2;
cout<<LCS(s1,s2,0,0)<<endl;
cin.get();
cin.get();
return 0;
}用递归的方法优点是编程简单,容易理解。缺点是效率不高,有大量的重复执行递归调用,而且只能求出最大公共子序列的长度,求不出具体的最大公共子序列。

(2)动态规划求最长公共子序列的长度。动态规划采用二维数组来标识中间计算结果,避免重复的计算来提高效率。

得到如下推导公式:



#include<iostream>
#include<string>
#include<algorithm>
const int MAX_LEN=1000;
int maxLen[MAX_LEN+1][MAX_LEN+1];
int flag[MAX_LEN+1][MAX_LEN+1];//1——斜向下走,2——向右标记,3——向下标记
using namespace std;
int LCS(string s1,string s2){
for(int i=0;i<=s1.length();i++) maxLen[i][0]=0;
for(int i=0;i<=s2.length();i++) maxLen[0][i]=0;
for(int i=1;i<=s1.length();i++){
for(int j=1;j<=s2.length();j++){
if(s1[i-1]==s2[j-1]){
maxLen[i][j]=maxLen[i-1][j-1]+1;
flag[i][j]=1;
}
else if(maxLen[i-1][j]>maxLen[i][j-1]){
maxLen[i][j]=maxLen[i-1][j];
flag[i][j]=2;
}
else {
maxLen[i][j]=maxLen[i][j-1];
flag[i][j]=3;
}
}
}
return maxLen[s1.length()][s2.length()];
}
void getLCS(string s1,string s2){//逆向找出最长公共子序列
string res="";
int i=s1.length();
int j=s2.length();
while(i>0&&j>0){
if(flag[i][j]==1){
res.append(1,s1[i-1]);
i--;
j--;
}
else if(flag[i][j]==2) i--;
else if(flag[i][j]==3) j--;
}
reverse(res.begin(),res.end());//要反着输出
cout<<res<<endl;
}
int main(){
string s1,s2;
cin >> s1>>s2;
memset(flag,0,sizeof(flag));
cout<<LCS(s1,s2)<<endl;
getLCS(s1,s2);
cin.get();
cin.get();
return 0;
}

图解如下:



动态规划采用二维数组来标识中间计算结果,避免重复的计算来提高效率。

二、计算两个字符串的最大公共子串(Longest Common Substring)的长度,字符不区分大小写。输入两个字符串,输出一个整数。例如:asdfas werasdfaswer,输出6.这里的最大公共字串要求的字串是连续的。

求解的方法和最大公共子序列差不多,只是在s1[i]!=s2[j]的时候,保存长度的值要重新记为0.

#include<iostream>
#include<string>
#include<algorithm>
using namespace std;
const int MAX_LEN=1000;
int maxLen[MAX_LEN+1][MAX_LEN+1];
int bigest=0;

int conLCS(string s1,string s2){

for(int i=0;i<=s1.length();i++) maxLen[i][0]=0;
for(int i=0;i<=s2.length();i++) maxLen[0][i]=0;
for(int i=1;i<=s1.length();i++){
for(int j=1;j<=s2.length();j++){
if(s1[i-1]==s2[j-1]){
maxLen[i][j]=maxLen[i-1][j-1]+1;
if(maxLen[i][j]>bigest) bigest=maxLen[i][j];//要保存最长的长度

}
else maxLen[i][j]=0;//相对于最长公共子序列,就这里改变了

}
}
return bigest;
}
void getLCS(string s1,string s2){
string res="";
for(int i=1;i<=s1.length();i++){
for(int j=1;j<=s2.length();j++){
if(maxLen[i][j]==bigest){
if(i-bigest+1>=0){
res=s1.substr(i-bigest,i);//输出有多个
cout<<res<<endl;
}
}
}
}
}
int main(){
string s1,s2;
cin >> s1>>s2;

cout<<conLCS(s1,s2)<<endl;
getLCS(s1,s2);
cin.get();
cin.get();
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐