利用后缀数组求字符串的最长重复子串
来源:互联网 发布:看刀路软件 编辑:程序博客网 时间:2024/05/01 08:26
以字符串aboreabo为例,把它的所有后缀按字典顺序排列后为:
abo
aboreabo
bo
boreabo
eabo
o
oreabo
reabo
这个字符串的后缀数组为{5,0,6,1,4,7,2,3}。
Manber和Myers在《Suffix arrays: A new method for on-line string searches》中提出后缀数组,并给出一种算法复杂度为O(nlogn)的算法,04年国家集训队有篇关于后缀数组的论文,就是用的这个算法。2006年,Karkkainen等人在《Linear Work Suffix Array Construction》中提出了DC3(difference cover)算法,可以在用O(n)的时间复杂度构建出后缀数组,并且在文章末尾给出了使用C++的实现代码。
后缀数组是个很好用的数据结构,在求单个字符串的重复子串和多个字符串的公共子串时,应该优先考虑是否可以使用后缀数组。
下面的程序就是利用后缀数组求一个字符串的最长公共子串的算法。这个算法主要利用各个后缀之间的关系,因为对于同一个字符串的任意两个后缀,较短后缀也是较长后缀的后缀。每轮排序都以相应后缀的定长前缀为关键字来排序,前缀长度的选取为2的倍数,分别为1,2,4,… 每轮排序后所得后缀数组依次记做S1,S2,S3,…
首先,以每个后缀的首字符为关键字,进行桶式排序,复杂度为O(n);
假如在当前阶段,关键字的长度为h,其中h为2的倍数,所有前h个关键字相同的后缀都存放在同一个h桶中;那么下个阶段排序使用的关键字长度为2h,排序时就可以使用前一阶段的结果,排序后前2h个关键字相同的后缀将存放在同一个2h桶中。
B1 B2 B3 B4 B5
——————— —— ——————— ——————— ———
atta... athl… envy… erro… er… hl… hl… ta…
↑Ai ↑Aj ↑Aj+h ↑Ai+h
上图所示是以前2个字符为关键字的第二轮排序完成后所得的后缀数组S2,图中后缀共存放在5个桶中,分别为B1,…,B5。在第三轮排序时,使用前4个字符为关键字,上轮排序后位于不同的桶中的后缀相对顺序已经确定,所以只需把上轮位于相同的桶中的后缀按前4个关键字划分为不同的桶即可。在本论中比较后缀Ai和Aj,由于Ai和Aj前2个关键字相同,Ai+h和Aj+h分别是Ai和Aj去掉前2个字符后的后缀,所以只需比较Ai+h和Aj+h即可,而在第二轮已经得到Ai+h和Aj+h的相对顺序,因Ai+h > Aj+h,所以得Ai > Aj。
B B2 B3 B4 B5 B6
——— ——— ——— ——————— ——————— ———
athl... atta… envy… erro… er… hl… hl… ta…
↑Ai<---->↑Aj ↑Aj+h ↑Ai+h
从头到尾遍历上一轮的后缀数组,对于每个Ai, 把Ai-h前移到所在桶的下一个填充位置,则得到本轮的后缀数组,然后可划分新的桶。当所有的元素都位于不同的桶时,排序完成,得到所需的后缀数组。
最多需排序O(logn)轮,每轮所需时间为O(n),所以算法总的复杂度为O(nlogn)。
下面是通过构造后缀数组来求给定字符串最长重复子串的程序。其中的两个子函数主要是JGShining实现的,但是我调试的时候发现存在的一些问题,对这两个函数加以改进和完善,主要修改了以下部分:
比如Rank数组越界,以及当字符串由同一字符重复组成时不能得出正确的height数组。
一、原方法在求S(2h)时采用逆向遍历后缀数组,由高端到低端的顺序填充桶;我使用正向遍历的方法,由低端到高端的顺序填充桶;
二、修改了Rank(2h)的计算方法,原方法在一些情况下不能得到正确的Rank数组;
三、修改了Height数组的计算方法,原方法在字符串由同一字符重复组成的情况下不能得到正确的height数组;
四、简化了两个子函数的接口。
JGShining的代码可以在http://jgshining.cn/blog/post/suffix_array_implemention.php找到。
//suffix_array.h
void CreateSuffixArray(const char str[], int length, int suff[], int rank[])
{
const int bucket_size = 256;
int *bucket = new int[bucket_size];
int idx;
for(idx = 0; idx < bucket_size; idx++)
bucket[idx] = 0;
for(idx = 0; idx < length; idx++)
bucket[str[idx]]++;
for(idx = 1; idx < bucket_size; idx++)
bucket[idx] += bucket[idx - 1];
for(idx = 0; idx < length; idx++)
suff[--bucket[str[idx]]] = idx;
for(rank[suff[0]] = 0, idx = 1; idx < length; idx++)
{
if(str[suff[idx]] == str[suff[idx-1]])
rank[suff[idx]] = rank[suff[idx - 1]];
else
rank[suff[idx]] = rank[suff[idx - 1]] + 1;
}
int *suff2 = new int[length];
int *rank2 = new int[length];
for(int h = 1; h < length && rank[suff[length - 1]] < length - 1; h *= 2)
{
for(idx = length - 1; idx >= 0; idx--)
{
bucket[rank[suff[idx]]] = idx;
}
for(idx = length - 1; idx >= length - h; idx--)
suff2[bucket[rank[idx]]++] = idx;
for(idx = 0; idx < length; idx++)
if(suff[idx] >= h)
suff2[bucket[rank[suff[idx] - h]]++] = suff[idx] - h;
for(idx = 0; idx < length; idx++)
suff[idx] = suff2[idx];
for(rank2[suff[0]] = 0, idx = 1; idx < length; idx++)
{
if(rank[suff[idx]] == rank2[suff[idx - 1]])
{
if(suff[idx] + h < length && suff[idx - 1] + h < length
&& rank[suff[idx] + h] == rank[suff[idx - 1] + h])
rank2[suff[idx]] == rank2[suff[idx - 1]];
else
rank2[suff[idx]] = rank2[suff[idx - 1]] + 1;
}
else
rank2[suff[idx]] = rank2[suff[idx - 1]] + 1;
}
for(idx = 0; idx < length; idx++)
rank[idx] = rank2[idx];
}
delete []bucket;
delete []suff2;
delete []rank2;
}
void GeneHeight(const char str[], int length, int suff[], int rank[], int hei
ght[])
{
for(int delta = 0, idx = 0; idx < length; idx++)
{
if(rank[idx] == 0)
height[rank[idx]] = 0;
else
{
int q = suff[rank[idx] - 1];
while(str[idx + delta] == str[q + delta])
delta++;
height[rank[idx]] = delta;
if(delta > 0)
delta--;
}
}
}
//suffix_array.cpp
#include "suffix_array.h"
#include <iostream>
#include <cstring>
using namespace std;
int main(int argc, char *argv[])
{
if(argc != 2)
{
cout<<"USAGE: "<<"./suffix_array <string>"<<endl;
return -1;
}
int size = strlen(argv[1]);
int *Suffix = new int[size];
int *Rank = new int[size];
int *Height = new int[size];
int idx;
CreateSuffixArray(argv[1], size, Suffix, Rank);
GeneHeight(argv[1], size, Suffix, Rank, Height);
int max = Height[0];
int max_idx = 0;
for(idx = 1; idx < size; idx++)
{
if(Height[idx] > max)
{
max = Height[idx];
max_idx = idx;
}
}
if(max > 0)
{
cout<<"The longest repeated substring is ";
for(idx = Suffix[max_idx]; idx < Suffix[max_idx] + max; idx++
)
cout<<argv[1][idx];
cout<<"."<<endl;
}
else
cout<<"There is no repeated substring."<<endl;
delete []Suffix;
delete []Rank;
delete []Height;
}
- 利用后缀数组求字符串的最长重复子串
- 利用后缀数组求最长的重复子串
- 使用后缀数组求字符串的最长重复子串
- 利用后缀数组求字符串的最长重复子串的算法 利用二维数组求两个字符串的最长公共字串(动态规划)
- 后缀数组求最长重复子串
- 后缀数组求最长重复子串
- 后缀数组求最长重复子串
- 后缀数组求最长重复子串
- 后缀数组求最长重复子串
- 后缀数组求最长重复子串
- 后缀数组求最长重复子串
- 后缀数组求最长重复子串
- 求最长重复子串(后缀数组)
- 后缀数组法求最长重复子串
- [转]后缀数组求最长重复子串
- SPOJ 687. Repeats(后缀数组求最长重复子串)
- 后缀数组应用(最长重复子字符串)
- 用后缀数组 求一个字符串的最长重复字串
- 随机取数据算法性能比较
- 20个最好的网站数据实时分析工具
- asp.net架构之请求处理过程:HttpModule,HttpHandler
- SWT如何给Table添加右键菜单
- ASP.NET页面执行顺序 (HttpModule,HttpHandler)
- 利用后缀数组求字符串的最长重复子串
- Oracle中Clob与Blob字段的读取
- SQL server 2005 如何批量修改架构名(包括表名和存储过程名)
- 找出数组中重复次数最多的元素并打印
- Sharepoint ListTemplateId
- C# API 调用格式和参数类型
- nasm下的int 3
- jquery模糊匹配
- 关于浏览器内核的一些小知识,明明白白选浏览器