动态规划之最长公共子序列
来源:互联网 发布:windows zip压缩命令 编辑:程序博客网 时间:2024/05/18 15:03
我们之前提到过过动态规划的几个经典问题:
动态规划原理:http://blog.csdn.net/ii1245712564/article/details/45040037
钢条切割问题:http://blog.csdn.net/ii1245712564/article/details/44464689
矩阵链乘法问题:http://blog.csdn.net/ii1245712564/article/details/44464689
今天我们来看一下动态规划的另外一个经典问题:最长公共子序列(longest-common-subsequence)
问题背景
在生物学中,经常需要比较两个不同生物的DNA,一个DNA串由由一串称为碱基的的分子组成,碱基有鸟嘌呤,腺嘌呤,胞嘧啶,胸腺嘧啶四中,我们用英文字母的首字母表示四种碱基,那么DNA就是在有限集{A,C,G,T}上的一个字符串。例如某种生物的DNA序列为:S1=ACCGGTCACGGTCGGAGTGGGAAC…,S2=GTAAAGTCACAAAACGTGT…,我们比较两个DNA串的原因就是希望确定他们的相似度,作为衡量两个物种相似度的标准。如果一个串是另外一个串的子串,那么它们就是相似的,但是这里彼此都不是彼此的子串。于是我们就有了新的衡量标准:寻找一个新串S3,使得S3中的元素都在S1,S2中出现过,且不要求连续,但是碱基在三个串中出现的顺序一定要相同。可以找到的S3的长度越长,表明两个物种越相似!
问题描述
给定两个字符串X[x1,x2,x3,x4,…,xn]和Y[y1,y2,y3,y4,…,ym],求X和Y的最长公共子序列LCS!
问题分析
首先我们尝试一下蛮力法,看看将其中一个序列X[x1,x2,…,xn]里面的所有子串xi提取出来,并与另外一个序列Y[y1,y2,…,ym]进行比较,检测子串xi中的元素是否在Y中按照相同的顺序出现。这里X的子序列一共有2^n个(每一个元素就是加入子串或者不加入子串两种选择,所以共有2*2*2*…2=2^n个),那么在和Y串进行对比的时候,每次都是O(m)。蛮力法运行时间是在指数级别的,要是两个串的长度长一点的话,比较一次都够你睡个午觉了。
那有没有更快的方法呢?动态规划上场。。。
问题解决
首先我们声明定义两种记法,方便后面的书写:我们记X[x1,x2,…,xn]为Xn,比如X2=[x1,x2]了
1.刻画最长公共子序列的特征
令X=[x1,x2,…,xn]和Y=[y1,y2,…,ym],并且他们的最长公共子序列为Z=[z1,z2,…,zk],那么我们有一下结论:
- 如果xn == ym, 则xn == ym == zk 且 Zk-1就是Xn-1和Ym-1的最长公共子序列。
- 如果xn != ym , 那么当xn != zk 时,Zk是Xn-1和Ym的最长公共子序列。
- 如果xn != ym , 那么当ym !=zk时,Zk是Xn和Ym-1的最长公共子序列。
证明:
- 在xn == ym时,如果zk != xn 或者zk !=ym,那么X和Y的最长公共子序列为Z+1,与原问题矛盾,所以在xn == ym时, xn == ym == zk成立。如果在xn == ym,Xn-1和Ym-1存在一个比k-1更长的公共子序列W,又因为xm == ym,W+1>Z,与原问题矛盾,不成立!
- 如果xn != zk,那么Zk是Xn-1和Ym的最长公共子序列,,如果存在一个长度大于k的序列W是Xn-1和Ym的最长公共子序列,那么W同样也是Xn和Ym的最长公共子序列,W>Z,与原问题矛盾!
- 同上可证!
我们看出,LCS问题是具有最优子结构的!
2.一个递归解
由上面得到的最长公共子序列的特征,我们知道,当xn == ym时,最长公共子序列长度就是在子问题算出的最长公共子序列长度上面+1即可,如果 xn != ym,那么最长公共子序列就是[Xn-1,Ym]和[Xn, Ym-1]中最长的哪一个!于是我们等到下面的递归解:
3.计算LCS长度
通过上面的递归式,我们现在采用两种方法来求解LCS的长度
/** 这里采用自顶向下的算法 */#include <iostream>#include <vector>#include <climits>using namespace std;unsigned int dealLCS(std::vector<char> X , std::vector<char> Y , std::vector<std::vector<unsigned int> > & tempData);/** * 计算最长公共子序列长度 * @param X X序列 * @param Y Y序列 * @return 最大长度 */size_t LCS(std::vector<char> &X , std::vector<char> &Y){ if(X.size() == 0 || Y.size() == 0) return 0; /** 创建一个数组来保存临时数据 */ std::vector<std::vector<unsigned int> > tempData(X.size()+1 , std::vector<unsigned>()); for (int i = 0; i <= X.size(); ++i) { for (int j = 0; j <= Y.size(); ++j) { if(i == 0 || j == 0) tempData[i].push_back(0); else tempData[i].push_back(UINT_MAX); } } dealLCS(X , Y , tempData);//开始进行计算 return tempData[X.size()][Y.size()];}/** * 实际计算LCS的最大长度 * @param X X序列 * @param Y Y序列 * @param tempData 缓存数据 */unsigned int dealLCS(std::vector<char> X , std::vector<char> Y , std::vector<std::vector<unsigned int> > & tempData){ if(X.size() == 0 || Y.size() == 0 ) return 0; //首先看看这个问题有没有被计算过 if(tempData[X.size()][Y.size()] != UINT_MAX) return tempData[X.size()][Y.size()]; //表示没有计算过,下面开始计算 unsigned int maxLength = 0; if(X[X.size()-1] == Y[Y.size()-1])// 最后两个元素相等的情况 { maxLength = dealLCS(std::vector<char>(X.begin() , X.end()-1),\ std::vector<char>(Y.begin() , Y.end()-1),\ tempData)+1; } else//最后两个元素不相等的情况 { unsigned int temp1 = dealLCS(std::vector<char>(X.begin() , X.end()-1),\ std::vector<char>(Y.begin() , Y.end()) , tempData); unsigned int temp2 = dealLCS(std::vector<char>(X.begin() , X.end()),\ std::vector<char>(Y.begin() , Y.end()-1), tempData); maxLength = temp1 > temp2 ? temp1 : temp2; } tempData[X.size()][Y.size()] = maxLength; return maxLength;} int main(int argc, char const *argv[]){ char array1[]={'A','B','C','B','D','A','B'}; char array2[]={'B','D','C','A','B','A'}; std::vector<char> X(array1 , array1+sizeof(array1)); std::vector<char> Y(array2 , array2+sizeof(array2)); cout<<"The Lcs length is :"<<LCS(X,Y)<<endl; return 0;}
下面这个是自底向上的方法:
/** 这里采用自底向上的算法实现 */#include <iostream>#include <vector>#include <climits>using namespace std;unsigned int dealLCS(std::vector<char> X , std::vector<char> Y , std::vector<std::vector<unsigned int> > & tempData);/** * 计算最长公共子序列长度 * @param X X序列 * @param Y Y序列 * @return 最大长度 */size_t LCS(std::vector<char> &X , std::vector<char> &Y){ if(X.size() == 0 || Y.size() == 0) return 0; /** 创建一个数组来保存临时数据 */ std::vector<std::vector<unsigned int> > tempData(X.size()+1 , std::vector<unsigned>()); for (int i = 0; i <= X.size(); ++i) { for (int j = 0; j <= Y.size(); ++j) { if(i == 0 || j == 0) tempData[i].push_back(0); else tempData[i].push_back(UINT_MAX); } } dealLCS(X , Y , tempData);//开始进行计算 return tempData[X.size()][Y.size()];}/** * 实际计算LCS的最大长度 * @param X X序列 * @param Y Y序列 * @param tempData 缓存数据 */unsigned int dealLCS(std::vector<char> X , std::vector<char> Y , std::vector<std::vector<unsigned int> > & tempData){ if(X.size() == 0 || Y.size() == 0) return 0; for (unsigned int i = 0; i < X.size(); ++i) { for (unsigned int j = 0; j < Y.size(); ++j) { if(X[i] == Y[j]) { tempData[i+1][j+1] = tempData[i][j]+1; } else { unsigned int temp1 = tempData[i+1][j]; unsigned int temp2 = tempData[i][j+1]; tempData[i+1][j+1] = temp1 > temp2 ? temp1 : temp2; } } } return tempData[X.size()][Y.size()];}int main(int argc, char const *argv[]){ char array1[]={'A','B','C','B','D','A','B'}; char array2[]={'B','D','C','A','B','A'}; std::vector<char> X(array1 , array1+sizeof(array1)); std::vector<char> Y(array2 , array2+sizeof(array2)); cout<<"The Lcs length is :"<<LCS(X,Y)<<endl; while(1); return 0;}
如果要了解自上而下和自下而上两种算法的区别,请见:
重叠子问题:http://blog.csdn.net/ii1245712564/article/details/45040037
上面两个算法里面都用到了tempData数组来保存零时数据,tempData[i][j]表示从
最终右下角的tempData[8][7]就是X8和Y7保存的最长公共子串的长度!
4.构造LCS
下面我们以上面的tempData构成的矩阵为基础,从右下角开始,寻找最长的子序列!
自上而下构造LCS
/** 自顶向下带有解决方案 */#include <iostream>#include <vector>#include <climits>using namespace std;unsigned int dealLCS(std::vector<char> X , std::vector<char> Y , std::vector<std::vector<unsigned int> > & tempData);void printSolution(std::vector<char> X ,\ std::vector<std::vector<unsigned int> > & solution ,\ size_t i , size_t j){ if(i == 0 || j ==0) return; if(solution[i][j] == solution[i-1][j]) printSolution(X,solution,i-1,j); else if(solution[i][j] == solution[i][j-1]) printSolution(X,solution,i,j-1); else if(solution[i][j]-1 == solution[i-1][j-1]) { cout<<i<<" "<<j<<" "<<X[i-1]<<"\n"; printSolution(X,solution , i-1 ,j-1); }}/** * 计算最长公共子序列长度 * @param X X序列 * @param Y Y序列 * @return 最大长度 */size_t LCS(std::vector<char> &X , std::vector<char> &Y){ if(X.size() == 0 || Y.size() == 0) return 0; /** 创建一个数组来保存临时数据 */ std::vector<std::vector<unsigned int> > tempData(X.size()+1 , std::vector<unsigned>()); for (int i = 0; i <= X.size(); ++i) { for (int j = 0; j <= Y.size(); ++j) { if(i == 0 || j == 0) tempData[i].push_back(0); else tempData[i].push_back(UINT_MAX); } } dealLCS(X , Y , tempData);//开始进行计算 printSolution(X,tempData,X.size(),Y.size()); return tempData[X.size()][Y.size()];}/** * 实际计算LCS的最大长度 * @param X X序列 * @param Y Y序列 * @param tempData 缓存数据 */unsigned int dealLCS(std::vector<char> X , std::vector<char> Y , std::vector<std::vector<unsigned int> > & tempData){ if(X.size() == 0 || Y.size() == 0 ) return 0; //首先看看这个问题有没有被计算过 if(tempData[X.size()][Y.size()] != UINT_MAX) return tempData[X.size()][Y.size()]; //表示没有计算过,下面开始计算 unsigned int maxLength = 0; if(X[X.size()-1] == Y[Y.size()-1])// 最后两个元素相等的情况 { maxLength = dealLCS(std::vector<char>(X.begin() , X.end()-1),\ std::vector<char>(Y.begin() , Y.end()-1),\ tempData)+1; } else//最后两个元素不相等的情况 { unsigned int temp1 = dealLCS(std::vector<char>(X.begin() , X.end()-1),\ std::vector<char>(Y.begin() , Y.end()) , tempData); unsigned int temp2 = dealLCS(std::vector<char>(X.begin() , X.end()),\ std::vector<char>(Y.begin() , Y.end()-1), tempData); maxLength = temp1 > temp2 ? temp1 : temp2; } tempData[X.size()][Y.size()] = maxLength; return maxLength;} int main(int argc, char const *argv[]){ char array1[]={'A','B','C','B','D','A','B'}; char array2[]={'B','D','C','A','B','A'}; std::vector<char> X(array1 , array1+sizeof(array1)); std::vector<char> Y(array2 , array2+sizeof(array2)); cout<<"The Lcs length is :"<<LCS(X,Y)<<endl; while(1); return 0;}
自下而上的构造LCS
/** 这里采用自底向上带有绝决方案的算法实现 */#include <iostream>#include <vector>#include <climits>using namespace std;unsigned int dealLCS(std::vector<char> X , std::vector<char> Y , std::vector<std::vector<unsigned int> > & tempData);void printSolution(std::vector<char> X ,\ std::vector<std::vector<unsigned int> > & solution ,\ size_t i , size_t j){ if(i == 0 || j ==0) return; if(solution[i][j] == solution[i-1][j]) printSolution(X,solution,i-1,j); else if(solution[i][j] == solution[i][j-1]) printSolution(X,solution,i,j-1); else if(solution[i][j]-1 == solution[i-1][j-1]) { cout<<i<<" "<<j<<" "<<X[i-1]<<"\n"; printSolution(X,solution , i-1 ,j-1); }}/** * 计算最长公共子序列长度 * @param X X序列 * @param Y Y序列 * @return 最大长度 */size_t LCS(std::vector<char> &X , std::vector<char> &Y){ if(X.size() == 0 || Y.size() == 0) return 0; /** 创建一个数组来保存临时数据 */ std::vector<std::vector<unsigned int> > tempData(X.size()+1 , std::vector<unsigned>()); for (int i = 0; i <= X.size(); ++i) { for (int j = 0; j <= Y.size(); ++j) { if(i == 0 || j == 0) tempData[i].push_back(0); else tempData[i].push_back(UINT_MAX); } } dealLCS(X , Y , tempData);//开始进行计算 printSolution(X,tempData,X.size(),Y.size()); return tempData[X.size()][Y.size()];}/** * 实际计算LCS的最大长度 * @param X X序列 * @param Y Y序列 * @param tempData 缓存数据 */unsigned int dealLCS(std::vector<char> X , std::vector<char> Y , std::vector<std::vector<unsigned int> > & tempData){ if(X.size() == 0 || Y.size() == 0) return 0; for (unsigned int i = 0; i < X.size(); ++i) { for (unsigned int j = 0; j < Y.size(); ++j) { if(X[i] == Y[j]) { tempData[i+1][j+1] = tempData[i][j]+1; } else { unsigned int temp1 = tempData[i+1][j]; unsigned int temp2 = tempData[i][j+1]; tempData[i+1][j+1] = temp1 > temp2 ? temp1 : temp2; } } } return tempData[X.size()][Y.size()];}int main(int argc, char const *argv[]){ char array1[]={'A','B','C','B','D','A','B'}; char array2[]={'B','D','C','A','B','A'}; std::vector<char> X(array1 , array1+sizeof(array1)); std::vector<char> Y(array2 , array2+sizeof(array2)); cout<<"The Lcs length is :"<<LCS(X,Y)<<endl; while(1); return 0;}
上面两种方法都是以tempData矩阵为基础开始的!
- 动态规划之最长公共子序列
- 动态规划之最长公共子序列
- 动态规划之最长公共子序列
- 动态规划之最长公共子序列
- 动态规划之最长公共子序列
- 动态规划之最长公共子序列
- 动态规划之最长公共子序列
- 动态规划之最长公共子序列
- 动态规划之最长公共子序列
- 动态规划之最长公共子序列
- 动态规划之最长公共子序列
- 动态规划之最长公共子序列
- 动态规划之最长公共子序列
- 动态规划之最长公共子序列
- 动态规划之最长公共子序列
- 动态规划之最长公共子序列
- 动态规划之最长公共子序列
- 动态规划之最长公共子序列
- JAVA深入研究——Method的Invoke方法。
- An internal error occurred during: "Launching spanking". java.lang.NullPointerException 安卓eclipse出现
- solr4.10.3部署tomcat7
- Mina学习的推荐方法
- jQuery的.live()和.die()
- 动态规划之最长公共子序列
- FFMPEG使用参数详解
- Android优秀学习资料(高手博客)
- 快速幂取模算法
- 360开源项目汇总(一)
- node.js学习笔记1——node.js是什么
- ADB报错
- There is no statement named *** in this SqlMap.
- The SELECT would examine more than MAX_JOIN_SIZE rows 报错分析 MYSQL