利用后缀数组(suffix array)求最长公共子串(longest common substring)
摘要:本文讨论了最长公共子串的的相关算法的时间复杂度,然后在后缀数组的基础上提出了一个时间复杂度为o(n^2*logn),空间复杂度为o(n)的算法。该算法虽然不及动态规划和后缀树算法的复杂度低,但其重要的优势在于可以编码简单,代码易于理解,适合快速实现。
首先,来说明一下,LCS通常指的是公共最长子序列(Longest Common Subsequence,名称来源参见《算法导论》原书第3版p223),而不是公共最长子串(也称为最长公共子串)。
最长公共子串问题是在文本串、模式串中寻找共有的一个最长的子串,如文本串text=“abcbcedf”,pattern=“ebcbcdf”,则最长公共子串为“bcbc”,长度为4。
最长公共子串的解法很多,有蛮力搜索法、动态规划法、后缀数组法、后缀树法。本文着重提后缀数组法,其他方法可以自行百度。
蛮力搜索法
int enum_longestCommonSubstring(char *text,char *pattern)
{
if(!text || !pattern) return ; //nullptr
int tlen=strlen(text),plen=strlen(pattern);
if(==tlen || ==plen) return ; //empty string
int maxLEN=,i=,j=,ofs=;
for(i=;i<tlen && (tlen-i>=maxLEN);++i)
for(j=;j<plen && (plen-j>=maxLEN); ++j)
if( *(text+i)==*(pattern+j) )
{
ofs=;
while((i+ofs)<tlen&&(j+ofs)<plen&&*(text+ofs)==*(pattern+ofs))
{ ++ofs; }
if(ofs>maxLEN) maxLEN=ofs; //update
}
return maxLEN;
}
记文本串长度为m,模式串长度为n,则暴力搜索法时间复杂度为o(m*n*Min(m,n)),空间复杂度o(1)。在子串匹配问题上,如果使用KMP算法,则算法效率可以提高。
动态规划
动态规划求解最长公共子串问题的时间复杂度为o(m*n),经过优化后的动态规划算法可以达到o(Min(m,n))的空间复杂度
参见http://www.cnblogs.com/ider/p/longest-common-substring-problem-optimization.html
后缀数组
利用排序后的后缀数组(suffix array)来求解最长公共子串步骤为:
一,拼接文本串和模式串得到一个新的串X;
二,将X的所有后缀数组存入sa;(文本串长度为m,模式串长n。步骤二时间复杂度o(m+n)
三,对sa进行排序;
四,计算sa中相邻的子串的最长公共前缀长度(时间复杂度o((m+n)*Min(m,n)))
注:为了避免得到单个串的最长重复子串,在步骤四种参与比较的两个子串应该为一个是文本串的子串,另一个为模式串的子串。因此,在步骤一、二中就应该附加记录位来处理。
《后缀数组——————处理字符串的有力工具处理字符串的有力工具》罗穗骞介绍了使用基数排序来排序后缀数组的方法,排序时间复杂度(m+n)*log(m+n)。因此,使用使用后缀数组+基数排序得到的算法的时间复杂度为o((m+n)*Min(m,n))(步骤四决定最大时间复杂度)。但是,该方法较复杂,不容易掌握,在此处,我提出一种后缀数组+C标准库sort排序的算法,其排序时间复杂度为o(Min(m,n)*(m+n)*log(m+n)),因此,算法整体的时间复杂度为o(Min(m,n)*(m+n)*log(m+n))(由步骤三决定最大时间复杂度),此外,该算法空间复杂度为o(m+n)。 “后缀数组+快排”算法时间复杂低于“后缀数组+基数排序”的时间复杂度,但优点在于利用标准库sort+strcmp来实现排序,代码简单,算法更容易理解。代码如下:
#include<stdio.h>
#include<iostream>
#include<string.h>
#include<algorithm>
using namesapce std;
int suffixArrayQsort_longestCommonSubstring(char *text,char *pattern)
{
if(!text || !pattern) return ; //nullptr
int tlen=strlen(text),plen=strlen(pattern),i,j;
if(==tlen || ==plen) return ; //empty string enum ATTRIB{TEXT,PATTERN};
struct absInfo
{
char *head;
ATTRIB attr; //tag
int len;
absInfo():head(NULL),attr(TEXT),len(){}
absInfo(char *phead,ATTRIB attrib,int length):head(phead),attr(attrib),len(length){}
bool operator < (const absInfo &b)
{
return strcmp(head,b.head)<;
}
static void display(const absInfo &a)
{
printf("size:%d type:%-7s ",a.len, (a.attr==TEXT?"TEXT":"PATTERN") );
printf("%s\n",a.head);
}
}*sa; //step 2:build the suffix array
sa=new absInfo[tlen+plen];
for(i=;i<tlen;++i)
{
sa[i].head=text+i;
sa[i].attr=TEXT;
sa[i].len=tlen-i;
}
for(j=;j<plen;++j)
{
sa[j+tlen].head=pattern+j;
sa[j+tlen].attr=PATTERN;
sa[j+tlen].len=plen-j;
} //step 3:use sort() to sort the sa
puts("before sort, the sa is:"); for_each(sa,sa+tlen+plen,absInfo::display);
sort(sa,sa+tlen+plen);
puts("after sort, the sa is:"); for_each(sa,sa+tlen+plen,absInfo::display); //step 4:compare
int maxLEN=,rec=;
for(i=;i<tlen+plen-;i++)
{
if(sa[i].attr==sa[i+].attr) continue;
if(sa[i].len<=maxLEN || sa[i+].len<=maxLEN) continue;
rec=;
while(rec<sa[i].len && rec<sa[i+].len && *(sa[i].head+rec)==*(sa[i+].head+rec) )
++rec;
if(rec>maxLEN) maxLEN=rec; //update
}
//release memory resource and return
delete [] sa; sa=NULL;
return maxLEN;
}
注:1,absInfo结构中len字段不是必须的,设置此字段只是为了在代码56行处做一个搜索剪枝操作。
2,稍微改动代码就能在算法中给出公共子串的值(对示例来说就是给出“bcbc"),通过absInfo的len字段和maxLEN值也可以在o(1)的时间复杂度内计算出公共子串分别在文本串和模式串中的位置
运行结果:
当文本串text=“abcbcedf”,pattern=“ebcbcdf”时,代码运行如下图所示:

从代码可以看出,“后缀数组+qsort排序”实现最长公共子串具有编码简单的特点,空间复杂度为o(m+n)
后缀树
后缀树以及广义的后缀树算法读者可以自行搜索。
利用后缀数组(suffix array)求最长公共子串(longest common substring)的更多相关文章
- 最长公共子串(Longest common substring)
问题描述: 给定两个序列 X=<x1, x2, ..., xm>, Y<y1, y2, ..., yn>,求X和Y长度最长的公共子串.(子串中的字符要求连续) 这道题和最长公共 ...
- 后缀数组(模板题) - 求最长公共子串 - poj 2774 Long Long Message
Language: Default Long Long Message Time Limit: 4000MS Memory Limit: 131072K Total Submissions: 21 ...
- POJ2774 Long Long Message —— 后缀数组 两字符串的最长公共子串
题目链接:https://vjudge.net/problem/POJ-2774 Long Long Message Time Limit: 4000MS Memory Limit: 131072 ...
- 后缀数组(suffix array)
参考: Suffix array - Wiki 后缀数组(suffix array)详解 6.3 Suffix Arrays - 算法红宝书 Suffix Array 后缀数组 基本概念 应用:字 ...
- poj 2774 Long Long Message,后缀数组,求最长公共子串 hdu1403
题意:给出两个字符串,求最长公共子串的长度. 题解:首先将两个字符串连在一起,并在中间加一个特殊字符(字串中不存在的)切割,然后两个串的最长公共字串就变成了全部后缀的最长公共前缀.这时就要用到heig ...
- 求最长公共子串 Longest Common Subsequence
最长公共子串 // Longest Common Subsequence 子串有别于子序列, 子串是连续的, 而子序列可以不连续 /*--------------------------------- ...
- POJ 2774 Long Long Message (二分 + Hash 求最长公共子串)题解
题意:求最长公共子串 思路:把两个串Hash,然后我们把短的作为LCS的最大可能值,然后二分长度,每次判断这样二分可不可以.判断时,先拿出第一个母串所有len长的子串,排序,然后枚举第二个母串len长 ...
- 文本比较算法Ⅱ——Needleman/Wunsch算法的C++实现【求最长公共子串(不需要连续)】
算法见:http://www.cnblogs.com/grenet/archive/2010/06/03/1750454.html 求最长公共子串(不需要连续) #include <stdio. ...
- 后缀数组(suffix array)详解
写在前面 在字符串处理当中,后缀树和后缀数组都是非常有力的工具. 其中后缀树大家了解得比较多,关于后缀数组则很少见于国内的资料. 其实后缀数组是后缀树的一个非常精巧的替代品,它比后缀树容易编程实现, ...
随机推荐
- QT正则表达式学习(Windows目录禁止九个字符)
exp 正则表达式30分钟入门教程 http://deerchao.net/tutorials/regex/regex.htm 元字符 .*^\d\b\s,当然还有\,还有中括号[] .是一个元字符, ...
- JPA概要
1 JPA概述 JPA(Java Persistence API,Java持久化API),定义了对象-关系映射(ORM)以及实体对象持久化的标准接口. JPA是JSR-220(EJB3.0)规范的一部 ...
- c++ smart pointer
智能指针(smart pointer)是存储指向动态分配(堆)对象指针的类,用于生存期控制,能够确保自动正确的销毁动态分配的对象,防止内存泄露.它的一种通用实现技术是使用引用计数(reference ...
- 「Poetize3」Heaven Cow与God Bull
描述 Description 给定一个整数n,求一个整数m,满足m<=n,并且m/phi(m)的值最大.注:phi(m)代表m的欧拉函数,即不大于m且与m互质的数的个数. 题解:m/phi(m) ...
- POJ-1330--Nearest Common Ancestors(离线LCA)
LCA离线算法 它需要一次输入所有的询问,然后有根节点开始进行深度优先遍历(DFS),在深度优先遍历的过程中,进行并查集的操作,同时查询询问,返回结果. 题意: 求A ,B两点的最近公共祖先 分析: ...
- Light OJ 1018 - Brush (IV)
题目大意: 一个二维平面上有N个点,一把刷子,刷一次可以把一条线上的所有点都刷掉.问最少刷多少次,可以把全部的点都刷完 状态压缩DP, 用记忆化搜索来写, 需要有个优化不然会超时. ===== ...
- 矩阵(快速幂):COGS 963. [NOI2012] 随机数生成器
963. [NOI2012] 随机数生成器 ★★ 输入文件:randoma.in 输出文件:randoma.out 简单对比 时间限制:1 s 内存限制:128 MB [问题描述] 栋 ...
- SRM 394(1-250pt)
DIV1 250pt 题意:给定一个字符串s('a'-'z'),计其中出现次数最多和最少的字母分别出现c1次和c2次,若在s中去掉最多k个字母,求去掉以后c1 - c2的最小值. 解法:做题的时候,想 ...
- Android少量数据保存之SharedPreferences接口实例
SharedPreferences数据保存主要是通过键值的方式存储在xml文件中 xml文件在data/此程序的包名/XX.xml 格式 <?xml version='1.0' encoding ...
- 在asp.net中使用confirm可以分为两种:
在asp.net中使用confirm可以分为两种: 1.没有使用ajax,confirm会引起也面刷新 2.使用了ajax,不会刷新 A.没有使用ajax,可以用StringBuilder来完成. ( ...