LCS的全称为Longest Common Subsequence,用于查找两个字符串中的最大公共子序列,这里需要注意区分子序列与子串,所谓子序列,指的是从前到后,可以跳跃元素筛选,而字串则必须连续筛选。

例如AB##!C!@#E和AB123CC321E两个字符串,如果找最长公共字串,只能是AB;如果是找最长公共子列,则是ABCE。

还有一种变种的LCS,允许元素重复,这样找到的子列将会是ABCCE,但是这样回溯是比较麻烦的,一般只能得到序··列的长度。

下面我们先介绍基本LCS的算法,然后介绍其变体。

【基本LCS】

1.首先作如下约定

①设字符串a、b的索引从1开始,a的全长为xm,b的全长为yn。

②c[i][j]记录了a串1~i范围和b串1~j范围内的最长子串长度。

2.递推式

要求c[i][j],我们需要考虑a[i]和b[j]的关系。

①a[i]=b[j]:说明当前子列的末尾是a、b所共有,各退一步,就得到了上一次求得的公共子列长度,也就是c[i-1][j-1],显然两个序列仅相差了一个字符,因此c[i][j] = c[i-1][j-1]+1。

②a[i]≠b[j]:说明当前子列的长度在a或者b向后推进一个字符后并未变化,因为这个字符不公共,应该考虑去掉一个字符后的公共子列中较长的,也就是c[i][j] = max{c[i-1][j], c[i][j-1]}。

这样,我们就得到了完整的递推式,下面要解决的就是递推起点的参数。

不难发现,c[0][.]和c[.][0]都应该是0,这就是递推的起点。

3.编程实现

从c[1][1]一直处理到c[xm][yn]即可,需要注意的是字符索引从0开始,因此我们需要在c的索引基础上减一。

代码如下:

void LCS(string a, string b){
int xm = a.length();
int yn = b.length();
vector<vector<int> > c(xm + 1);
for(int x = 0; x <= xm; x++){
c[x].resize(yn + 1);
}
for(int x = 1; x <= xm; x++) c[x][0] = 0;
for(int y = 1; y <= yn; y++) c[0][y] = 0;
for(int x = 1; x <= xm; x++)
for(int y = 1; y <= yn; y++){
if(a[x-1] == b[y-1]){
c[x][y] = c[x-1][y-1] + 1;
}else if(c[x][y-1] >= c[x-1][y]){
c[x][y] = c[x][y-1];
}else{
c[x][y] = c[x-1][y];
}
}
printf("LCS Length:%d\n",c[xm][yn]);
}

这样仅仅能得到序列的长度,如果要得到子列,需要回溯,从a和b的最后一个字符开始,根据字符关系查表c来确定是否是子列中的元素。因为这样得到的是倒序,因此需要每次插入到字符串的头部。

    string res = "";
int i = xm, j = yn;
while(i >= 1 && j >= 1){
if(a[i-1] == b[j-1]){
res.insert(res.begin(),a[i-1]);
i--;
j--;
}else if(c[i][j-1] >= c[i-1][j]) j--;
else i--;
}
cout << res << endl;

【变种LCS】

如上文所述,有时候需要考虑元素重复的情况,例如PAT上的一道题1045.
Favorite Color Stripe (30)
就要求计算元素可重复的最长子列,为了达到这个目的,只需要对算法稍加修改,无论什么情况,均取c[i-1][j]、c[i][j-1]和c[i-1][j-1]中的最大值,并且如果发现a、b当前字串的末尾相同,则在最大值基础上+1,这样就可以重复记录了。

这样做的原因是,原来每次碰到相同的都去找去掉后的+1,这样即使碰到多次重复也不会造成累加,因为我们只考虑了对角线。而如果是在左、对角、上三个方向寻找最大的,则会不断累加。把两个字符串看成一张表,横着为串b,竖着为串a,假设此时比较的是a中的'x',而b中有多个'x',如果是普通LCS,对于每个‘x'都会去找对角线,这样不会造成累加,而变种LCS会找到左边已经累加过的再累加,因此就允许了重复计数。

这样处理的问题在于无法通过回溯得到序列,而只能拿到长度。

void LCS_changed(string a, string b){
int xm = a.length();
int yn = b.length();
vector<vector<int> > c(xm + 1);
for(int x = 0; x <= xm; x++){
c[x].resize(yn + 1);
}
for(int x = 1; x <= xm; x++) c[x][0] = 0;
for(int y = 1; y <= yn; y++) c[0][y] = 0;
for(int x = 1; x <= xm; x++)
for(int y = 1; y <= yn; y++){
int max = c[x-1][y-1];
if(c[x][y-1] > max) max = c[x][y-1];
if(c[x-1][y] > max) max = c[x-1][y];
if(a[x-1] == b[y-1]){
c[x][y] = max + 1;
}else{
c[x][y] = max;
}
}
printf("LCS Length:%d\n",c[xm][yn]);
}

对LCS算法及其变种的初步研究的更多相关文章

  1. 所有不同的序列串-----LCS算法的变种

    今天遇到LEETCODE的第115题: Distinct Subsequences Given a string S and a string T, count the number of disti ...

  2. LCS算法

    转自:http://hzzy-010.blog.163.com/blog/static/79692381200872024242126/  好详细~~~也十分好理解~~~ 最长公共子序列问题(非连续的 ...

  3. iOS多线程的初步研究(六)

    iOS多线程的初步研究(六) iOS平台提供更高级的并发(异步)调用接口,让你可以集中精力去设计需完成的任务代码,避免去写与程序逻辑无关的线程生成.运行等管理代码.当然实质上是这些接口隐含生成线程和管 ...

  4. iOS多线程的初步研究3

    iOS多线程的初步研究(三) 弄清楚NSRunLoop确实需要花时间,这个类的概念和模式似乎是Apple的平台独有(iOS+MacOSX),很难彻底搞懂(iOS没开源,呜呜). 官网的解释是说run ...

  5. iOS多线程的初步研究1

    iOS多线程的初步研究(一) 对于多线程的开发,iOS系统提供了多种不同的接口,先谈谈iOS多线程最基础方面的使用.产生线程的方式姑且分两类,一类是显式调用,另一类是隐式调用. 一.显示调用的类为NS ...

  6. iOS多线程的初步研究

    iOS多线程的初步研究(四) 理解run loop后,才能彻底理解NSTimer的实现原理,也就是说NSTimer实际上依赖run loop实现的. 先看看NSTimer的两个常用方法: + (NST ...

  7. <路径算法>哈密顿路径变种问题(2016华为软件精英挑战赛初赛)

    原创博客,转载请联系博主! 前言:几天前华为的这个软件精英(算法外包)挑战赛初赛刚刚落幕,其实这次是我第二次参加,只不过去年只入围到了64强(32强是复赛线),最后搞到了一个华为的一顶帽子(感谢交大l ...

  8. O(nlogn)LIS及LCS算法

    morestep学长出题,考验我们,第二题裸题但是数据范围令人无奈,考试失利之后,刻意去学习了下优化的算法 一.O(nlogn)的LIS(最长上升子序列) 设当前已经求出的最长上升子序列长度为len. ...

  9. LCS 算法实现

    动态规划算法 #include <iostream> #include <string.h> #include <algorithm> #include <m ...

随机推荐

  1. bzoj 5285: [Hnoi2018]寻宝游戏

    Description Solution 把输入的 \(n\) 个二进制数看作一个大小为 \(n*m\) 的矩阵 把每一列压成一个二进制数,其中最高位是最下面的元素 然后就有了 \(m\) 个二进制数 ...

  2. noip模拟题-赛斯石

    题目背景 白露横江,水光接天,纵一苇之所如,凌万顷之茫然.--苏轼 真程海洋近来需要进购大批赛斯石,你或许会问,什么是赛斯石? 首先我们来了解一下赛斯,赛斯是一个重量单位,我们用sisi作为其单位.比 ...

  3. 【HNOI2017】大佬

    题目描述 人们总是难免会碰到大佬.他们趾高气昂地谈论凡人不能理解的算法和数据结构,走到任何一个地方,大佬的气场就能让周围的人吓得瑟瑟发抖,不敢言语. 你作为一个 OIER,面对这样的事情非常不开心,于 ...

  4. Ubuntu 16.04 LTS安装搜狗拼音输入法网易云音乐 Remarkable

    第一步 首先在官网上面,下载最新的搜狗拼音输入法 Linux 版本. 第二步 进入命令行 Ctrl+Alt+T sudo dpkg -i sogoupinyin_2.1.0.0082_amd64.de ...

  5. Python 中的 if __name__ == '__main__' 该如何理解

    __name__ 表示当前模块名, __main__ 表示正在运行的模块名. if __name__ == '__main__' 这句话的意思就是,当模块被直接运行时,以下代码块将被运行,当模块是被导 ...

  6. Android SDK下载安装及配置教程

    2017年12月07日 13:33:32 4942 转载自:http://blog.csdn.net/dr_neo/article/details/49870587 Android开发环境搭建可以分为 ...

  7. java如何获得数据库表中各字段的字段名

    public class TestDemo { public static Connection getConnection() { Connection conn = null; try { Cla ...

  8. 如何上传本地项目到gitHub解决方案

    最近有人有人问到我怎么将新创建的本地代码上传到github上,这里简单的记录一下,我喜欢使用命令行,这里全用命令行来实现,不了解Git命令的可以去了解下. 1.  建立本地仓库,cd到你想要上传文件的 ...

  9. Logistic Regression 算法向量化实现及心得

    Author: 相忠良(Zhong-Liang Xiang) Email: ugoood@163.com Date: Sep. 23st, 2017 根据 Andrew Ng 老师的深度学习课程课后作 ...

  10. PHP XML SimpleXML

    PHP 可以基于 SimpleXML 生成和解析 xml 的方法,通过本节的实例,你将了解 PHP 是如何使用 SimpleXML 生成及解析 xml 格式数据的. PHP SimpleXML 处理最 ...