LCS算法
最长公共子序列问题(非连续的)
首先将要看到如何运用动态编程查找两个 DNA 序列的最长公共子序列(longest common subsequence,LCS)。发现了新的基因序列的生物学家通常想知道该基因序列与其他哪个序列最相似。查找 LCS 是计算两个序列相似程度的一种方法:LCS 越长,两个序列越相似。
子序列中的字符与子字符串中的字符不同,它们不需要是连续的。例如,ACE 是 ABCDE 的子序列,但不是它的子字符串。请看下面两个 DNA 序列:
- S1 = DE<GCCCTAGCGDE<
- S2 = DE<GCGCAATGDE<
这两个序列的 LCS 是 GCCAG。(请注意,这仅是一个 LCS,而不是唯一的 LCS,因为可能存在其他长度相同的公共子序列。这种最优化问题和其他最优化问题的解可能不止一个。)
首先,考虑如何递归地计算 LCS。令:
- C1 是 S1 最右侧的字符
- C2 是 S2 最右侧的字符
- S1' 是 S1 中 “切掉” C1 的部分
- S2' 是 S2 中 “切掉” C2 的部分
有三个递归子问题:
- L1 = LCS(S1', S2)
- L2 = LCS(S1, S2')
- L3 = LCS(S1', S2')
结果表明(而且很容易使人相信)原始问题的解就是下面三个子序列中最长的一个:
- L1
- L2
- 如果 C1 等于 C2,则为 L3 后端加上 C1 ,如果 C1 不等于 C2,则为 L3。
(基线条件(base case)是 S1 或 S2 为长度为零的字符串的情形。在这种情况下,S1 和 S2 的 LCS 显然是长度为零的字符串。)
但是,就像计算斐波纳契数的递归过程一样,这个递归解需要多次计算相同的子问题。可以证明,这种递归解法需要耗费指数级的时间。相比之下,这一问题的动态编程解法的运行时间是 Θ(mn),其中 m 和 n 分别是两个序列的长度。
为了用动态编程有效地计算 LCS,首先需要构建一个表格,用它保存部分结果。沿着顶部列出一个序列,再沿着左侧从上到下列出另一个序列,如图 2 所示:
这种方法的思路是:将从上向下、从左到右填充表格,每个单元格包含一个数字,代表该行和该列之前的两个字符串的 LCS 的长度。也就是说,每个单元格包含原始问题的一个字问题的解。例如,请看第 6 行第 7 列的单元格:它在 GCGCAATG 序列第二个 C 的右侧,在 GCCCTAGCG 的 T 的下面。这个单元格最终包含的数字就是 GCGC 和 GCCCT 的 LCS 的长度。
首先看一下表格的第二行中应该是什么条目。这一行的单元格保存的 LCS 长度对应的是序列 GCGCAATA 的零长前端和序列 GCCCTAGCG 的 LCS。显然,这些 LCS 的值都是 0。类似的,沿着第二列向下的值也都是 0,这与递归解的基线条件对应。现在表格如图 3 所示:
接下来,要实现与递归算法中递归子问题对应的情景,但这时使用的是表格中已经填充的值。在图 4 中,我已经填充了一半左右的单元格:
在填充单元格时,需要考虑以下条件:
- 它左侧的单元格
- 它上面的单元格
- 它左上侧的单元格
下面三个值分别对应着我在前面列出的三个递归子问题返回的值。
- V1 = 左侧单元格的值
- V2 = 上面单元格的值
- V3 = 左上侧单元格的值
在空单元格中填充下面 3 个数字中的最大值:
- V1
- V2
- 如果 C1 等于 C2 则为 V3 + 1,如果 C1 不等于 C2,则为 V3 ,其中 C1 是当前单元格上面的字符,C2 是当前单元格左侧的字符
请注意,我在图中还添加了箭头,指向当前单元格值的来源。后面的 “回溯” 一节将用这些箭头建立实际的 LCS(与仅仅发现 LCS 长度相反)。
现在填充图 4 中接下来的空单元格 — 在 GCCCTAGCG 中第三个 C 下面和 GCGCAATG 第二个 C 的右侧的单元格。它上面的值是 2,左侧的值是 3,左上侧的值是 2。这个单元格上面的字符和左侧的字符相等(都是 C),所以必须选择 2、3 和 3(左上侧单元格中的 2 + 1)的最大值。所以,这个单元格的值为 3。绘制一个箭头,从该单元格指向其中的值的源单元格。在这个示例中,新的数值可能来自不止一个单元格,所以可以任选一个:例如左上侧单元格。
作为练习,您可以尝试填充表格的余下部分。如果在关联过程中,一直按照左上侧-上侧-左侧的顺序选择单元格,那么会得到如图 5 所示的表格。(当然,如果在关联过程中做了不同的选择,那么箭头就会不同,但是数字是相同的。)
回想一下,任何单元格中的数字都是该单元格所在行之上和列之前的字符串的 LCS 长度。所以,表格右下角的数字就是字符串 S1 和 S2 (在本例中是 GCCCTAGCG 和 GCGCAATG)的 LCS 的长度。所以,这两个序列的 LCS 长度是 5。
这是在所有动态编程算法中都要牢记的关键点。表格中的每个单元格都包含单元格所在行上面和所在列左侧序列前端问题的解。
接下来要做的就是寻找实际的 LCS。使用单元格箭头进行回溯可以完成。在构建表格的时候,请记住,如果箭头指向左上侧的单元格,那么当前单元格中的值要比左上侧单元格的值大 1,这意味着左侧单元格和上面单元格中的字符相等。构建 LCS 时,这会将相应的字符添加到 LCS 中。所以,构建 LCS 的途径就是从右下角的单元格开始,沿着箭头一路返回。每当沿着对角箭头回到左上角的单元格而且 该单元格的值比当前单元格的值小 1 时,就要将对应的公共字符添加到 正在构建的 LCS 的前端。请注意,之所以将字符放在 LCS 前端,是因为我们是从 LCS 末端开始的。(在 图 5 的示例中,右下角的 5 与要添加的第 5 个字符对应。)
依此类推,继续构建 LCS。从右下侧的单元格开始,看到单元格指针指向左上侧的单元格,而且当前单元格的值(5)比其左上侧单元格的值(4)大 1。所以将字符 G 添加到最初的零长度的字符串之前。下一个箭头,从包含 4 的单元格开始,也指向左上侧,但是值没有变。接着这个箭头也是如此。下一个单元格的箭头还是指向左上侧,但是这次值从 3 变为 4。这意味着需要将这一行和这一列中的公共字符 A 添加到 LCS 中。所以现在的 LCS 是 AG。接下来,沿着指针向左(对应着跳过上面的 T)到达另一个 3。然后有一个对角指针指向 2。因此,又添加了在当前行和当前列中的公共字符 C,生成的 LCS 为 CAG。继续使用这种方式,直到最后到达 0。图 6 显示了整个回溯过程:
通过这种回溯方法,得到的 LCS 为 GCCAG
代码:
#include <iostream>
#include <string>
#include <cstring>
using namespace std; int c[][];
int b[][]; int LCS_Length(string x,string y)
{
int m=x.length();
int n=y.length();
int i,j; //根据递归方程的第一种情况,先初始化数组c[][]
memset(c,,sizeof(c)); for(i=;i<=m;i++)
for(j=;j<=n;j++)
{
//递归方程case 2
if(x[i-] == y[j-])
{
c[i][j]=c[i-][j-]+;
b[i][j]=; //表示↖
}
else if(c[i-][j] >= c[i][j-]) //下面是递归方程case3
{
c[i][j]=c[i-][j];
b[i][j]=; //表示↑
}
else
{
c[i][j]=c[i][j-];;
b[i][j]=; //表示←
}
}
return c[m][n];
} //输出最优解
void Print_LCS(string X,int i,int j)
{
if( (i == ) || (j == ) )
return;
if(b[i][j] == )
{
Print_LCS(X,i-,j-);
cout<<X[i-]<<" ";
}
else if(b[i][j] == )
Print_LCS(X,i-,j);
else
Print_LCS(X,i,j-);
} int main()
{
string X,Y;
while(cin>>X>>Y)
{
int p=LCS_Length(X,Y);
cout<<"这两个字符串的LCS为:"<<p<<endl;
cout<<"该公共子序列为:";
Print_LCS(X,X.length(),Y.length());
cout<<endl;
}
return ;
}
LCS算法的更多相关文章
- O(nlogn)LIS及LCS算法
morestep学长出题,考验我们,第二题裸题但是数据范围令人无奈,考试失利之后,刻意去学习了下优化的算法 一.O(nlogn)的LIS(最长上升子序列) 设当前已经求出的最长上升子序列长度为len. ...
- LCS 算法实现
动态规划算法 #include <iostream> #include <string.h> #include <algorithm> #include <m ...
- Levenshtein Distance + LCS 算法计算两个字符串的相似度
//LD最短编辑路径算法 public static int LevenshteinDistance(string source, string target) { int cell = source ...
- 对LCS算法及其变种的初步研究
LCS的全称为Longest Common Subsequence,用于查找两个字符串中的最大公共子序列,这里需要注意区分子序列与子串,所谓子序列,指的是从前到后,可以跳跃元素筛选,而字串则必须连续筛 ...
- 求两个字符串最长子串的LCS算法 C语言实现(简短的实现函数)
/************************************************************************* > File Name: lcs.c > ...
- LCS算法思想
LCS问题就是求两个字符串最长公共子串的问题.解法就是用一个矩阵来记录两个字符串中所有位置的两个字符之间的匹配情况,若是匹配则为1,否则为0.然后求出对角线最长的1序列,其对应的位置就是最长匹配子串的 ...
- LCS 算法
下面的程序分别实现了使用LCS求连续子串和不连续子串的匹配情况! http://beyond316.blog.51cto.com/7367775/1266360
- 所有不同的序列串-----LCS算法的变种
今天遇到LEETCODE的第115题: Distinct Subsequences Given a string S and a string T, count the number of disti ...
- 奇妙的算法之LCS妙解
LCS算法妙解 LCS问题简述:最长公共子序列 一个数列 S,如果分别是两个或多个已知数列的子序列,且是所有符合此条件序列中最长的,则S 称为已知序列的最长公共子序列. LCS问题的分支:最长公共子串 ...
随机推荐
- 在Apache下开启SSI配置支持include shtml html和快速配置服务器
作为前端开发,使用Apache快速搭建服务器极为方便. 1.找到apach安装目录,找到conf目录下 的httpd.conf 使用SSI(Server Side Include)的html文件扩展名 ...
- Linq表连接大全(INNER JOIN、LEFT OUTER JOIN、RIGHT OUTER JOIN、FULL OUTER JOIN、CROSS JOIN)
我们知道在SQL中一共有五种JOIN操作:INNER JOIN.LEFT OUTER JOIN.RIGHT OUTER JOIN.FULL OUTER JOIN.CROSS JOIN 1>先创建 ...
- hbuilder工具快捷键 http://www.qq210.com/shoutu/android
http://www.qq210.com/shoutu/android 创建HTML结构: h 8 (敲h激活代码块列表,按8选择第8个项目,即HTML代码块,或者敲h t Enter)中途换行: ' ...
- nodejs中间层现实
初次接触nodejs,是一种非常神奇的东西,未来必火起来.个人觉得最大优势npm命令. 闲话少说,直入主题.这是一个博客项目,php最为服务端,提供数据给node:nodejs+express作为中间 ...
- su: Bad item passed to pam_*_item()
su: Bad item passed to pam_*_item() 查看 /etc/default/locale 该文件应该只含义如下格式的文字: LANG=en_US.UTF-8 如何还没有解决 ...
- Js Carousel
http://getbootstrap.com/javascript/#carousel http://owlgraphic.com/owlcarousel/#demo https://www.mob ...
- Steam游戏黑屏与游戏直接安装方法
黑屏原因主要是360禁止了反作弊进程,解决方法: Steam游戏目录: Steam\steamapps\common\*** 例如黎明杀机,备份时候可直接备份该目录下 Dead by Daylight ...
- 实战 SSH 端口转发
转自实战 SSH 端口转发 通过本文的介绍,读者可以从中了解到如何应用 SSH 端口转发机制来解决日常工作 / 生活中的一些问题.学会在非安全环境下使用端口转发来加密网络应用,保护个人隐私以及重要商业 ...
- Windows Xp Home Edition 安装IIS组件
问题描述: 在虚拟机(操作系统是Windows Xp Home Edition)中安装Sql Server 2005的时候警告缺少IIS相关组件,控制面板"添加/删除组件"中也没有 ...
- SET NOCOUNT用法
当 SET NOCOUNT 为 ON 时,不返回计数(表示受 Transact-SQL 语句影响的行数). 当 SET NOCOUNT 为 OFF 时,返回计数. 如果存储过程中包含的一些语句并不返回 ...