您的位置:首页 > 其它

算法设计之最长公共子序列(LCS)问题

2014-10-04 20:09 281 查看

一、动态规划

1、什么是动态规划

先解释一下什么是动态规划,动态规划的英文名字是:Dynamic Programming ,首先要说明的是这里的Programming和程序设计里面的编程没有关系,而是指表格查询法(tabular method),即将每一步计算的结果存储在表格里,供随后的计算查询使用。

有动态规划的名字可以看出它和分而治之策略之间的不同。即在动态规划下,我们将问题分解为小问题,但是分解的小问题有许多是重复的,这样的话用表格将已经计算出的结果存起来可以节省重复计算,从而降低时间复杂性。这一点即是动态规划提出来的一个目的。

动态规划提出来的另一个主要目的是优化,即我们不只是要解决一个问题,而是要以最优的方式解决这个问题,或者说,针对特定问题寻求特优解。

2、动态规划的特点

1)分析一个最优解决方案应该具备的结构。

2)递归定义最优解决方案。

3)由底至上构建一个最优解决方案。

二、最长公共子序列(LCS-Longest Common Subsequence )

1、基本概念

子串(Substring):串的一个连续的部分,

子序列(Subsequence):是从不改变序列的顺序,而从序列中去掉任意的元素而获得的新序列。

更简略地说,子串的字符的位置必须连续,子序列则不必。

公共子序列(Common Subsequence):给定两个序列X和Y,如果Z既是X的子序列,也是Y的子序列,那么我们称Z是X和Y的公共子序列。

最长公共子序列(Longest Common Subsequence):最长的公共子序列。LCS可能不止一个。

2、最长公共子序列问题

背景不多介绍,在生物应用中很有用,可以用于比较两段DNA的相似程度。

先举例,说需求。

例如两个字符串X=ABCBDAB ,Y=BDCABA,序列BCA是X和Y的一个公共子序列,但是不是X和Y的最长公共子序列,子序列BCBA是X和Y的一个LCS,序列BDAB也是。

最长公共子序列问题就是需要在给定的两个字符串中找到最长的这样一个序列。

3、解决思路

3.1.穷举法

解最长公共子序列问题时最容易想到的算法是穷举搜索法,即对X的每一个子序列,检查它是否也是Y的子序列,从而确定它是否为X和Y的公共子序列,并且在检查过程中选出最长的公共子序列。X和Y的所有子序列都检查过后即可求出X和Y的最长公共子序列。X的一个子序列相应于下标序列{1, 2, …, m}的一个子序列,因此,X共有2m个不同子序列(Y亦如此,如为2^n),从而穷举搜索法需要指数时间(2^m * 2^n)。

3.2.递归

相比较于穷举法,可以使用递归的思想来解决LCS问题。递归的方法来解决这个问题和穷举法比较类似,而且效率还是很低的,但是相对来说比较容易理解。

下面大致说一下算法的思想。

人脑解决此类问题的方法就是逐个比较两个字符串的每个字符,比如str1[i]和str2[j],如果str1[i]==str2[j],则将str1[i]或str2[j]附加到str1[i]和str2[j]之前计算得到的最长公共子串后面,然后继续计算str1[i+1]开始的子串和和str2[j+1]开始的子串的最长公共子串。如果str1[i]!=str2[j],则采用三种方法穷举,第一种方法是删除str1[i],继续计算str1[i+1]开始的子串和str2[j]开始的子串的最长公共子串;第二种方法是删除str2[j],继续计算str1[i]开始的子串和str2[j+1]开始的子串的最长公共子串;第三种方法是删除str1[j]和str2[j],继续计算str1[i+1]开始的子串和str2[j+1]开始的子串的最长公共子串。使用三种方法计算完成后比较结果,取最长的子串附加到str1[i]和str2[j]之前计算得到的最长公共子串后面组成新的最长公共子串。以上继续计算都是递归过程,递归的终止条件即递归基是到达str1字符串结尾或str2字符串结尾。

实现代码:

#include<iostream>
#include<string>
using namespace std;
void RecursionLCS(const std::string& str1, const std::string& str2, std::string& lcs);
string GetLongestString(std::string& str1, std::string& str2,std::string& str3);
int main(){
	string x = "ABCBDAB" , y = "BDCABA"  ,lcs;//x和y是两个序列,需要求他们两个的LCS,lcs在程序最后存入的即为前两个序列的公共子序列。lcs的长度就是公共子序列的长度。
	RecursionLCS(x, y, lcs);
	cout << lcs.length()<< endl;
	for (int i= 0 ; i < lcs.length();i++){
		cout << lcs[i] ;				
	}
	cout << endl;
}
 void RecursionLCS(const std::string& str1, const std::string& str2, std::string& lcs)
{
		if (str1.length() == 0 || str2.length() == 0)
			return;
		else if (str1[0] == str2[0]){
			lcs += str1[0];
			RecursionLCS(str1.substr(1), str2.substr(1), lcs);
		}
		else{
			//cout<<"I am running" <<endl;
			std::string strTmp1, strTmp2, strTmp3;
			RecursionLCS(str1.substr(1), str2, strTmp1);
			RecursionLCS(str1, str2.substr(1), strTmp2); 
			RecursionLCS(str1.substr(1), str2.substr(1), strTmp3); 
			lcs += GetLongestString(strTmp1, strTmp2, strTmp3);
		}
}
string GetLongestString(std::string& str1, std::string& str2, std::string& str3){
	string longest = str1;
	if (longest.length()< str2.length()) longest = str2;
	if (longest.length() < str3.length()) longest = str3;
	return longest;
}


3.3.动态规划

#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
void DynamicLCS(const std::string& str1, const std::string& str2, std::string& lcs);
int main() {
	string x = "ABCBDAB" , y = "BDCABA";
	string lcs;
	DynamicLCS(x,y,lcs);
	cout << lcs << endl;
	
}

void DynamicLCS(const std::string& x, const std::string& y, std::string& lcs){
	int opt[x.length()+1][y.length()+1];
	for (int i = x.length() - 1; i >= 0; i--){  
		for (int j = y.length() - 1; j >= 0; j--){  
			if (x[i] == y[j])  
				opt[i][j] = opt[i + 1][j + 1] + 1;
			else  
				opt[i][j] = max(opt[i + 1][j], opt[i][j + 1]);
		}
	}
	int i =0,j = 0;
	while (i< x.length()&&j<y.length()){
		if (x[i]==y[j]){
			lcs +=x[i];
			i++;
			j++;
		}
		else if (opt[i+1][j] >= opt[i][j+1])
			i++;
		else 
			j++;
	}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐