您的位置:首页 > 其它

后缀树与后缀数组

2016-08-12 11:14 127 查看
后缀树和后缀数组是字符串处理的两大神器,几乎可处理掉一切的字符串处理问题,但是在实际中,后缀数组比后缀树更好写、好调,同时时间上也不差(常数很小),所以后缀数组绝对是OI竞赛之必备神器。

后缀树,实际上就是一棵字典树。考虑将某个串S的所有后缀插到一棵Trie里,那么我们就得到了一棵后缀树。在这里,我们不会讲后缀树的构造,只会略微讲一点其的思想。

在后缀树而言,匹配就变成一件易事了。考虑一个字符串匹配问题,如果我们要在一个串A中找很多个字符串B=b0,b1,b2…,那么也只需建好A的后缀树,然后直接放就好了……

另外,还有许多扩展应用,但在这里我就不细讲了。下面来讲后缀数组。

同样将一个字符串S的所有后缀,按照字典序排序,那么我们就得到了后缀数组。也就是说,后缀数组的第i个正是字典序第i大的后缀的编号。

好的,那么怎么构造呢?一种方法是从排序入手,由于字符串的比较是Θ(n)的,所以说再乘上排序的比较次数(如使用快排)nlog2n,那么总的时间复杂度就是O(n2log2n)。

但是,后缀有一些特殊的性质,可以帮我们优化排序。

我们要用的方法,是倍增算法,如果,我们每一次比较的时候,并不比较整个后缀,而是每次只比较一部分

首先,我们将每个后缀的第一个字符排序,做出第一轮的sa(也就是Suffix Array,后缀数组),然后排第二轮,这次排两个字符,相当于是给二元组排序。

然后,我们开始排四个字符,注意到,由于4=2+2,所以实际上……我们如果利用上一轮的排名,比如我们搞出另一个rank,使rank(i)为第i个后缀的排名(可能重叠),然后,假设我们现在要排后缀j的前四个字符,那么我们将这四个字符拆开,我们就可以得到前面的两个字符和后面的两个字符。前面的两个字符的排名正好是rank(j),而后面的两个字符,则是后缀j+2的前两个字符,其的排名是rank(j+2)。另外,我们来顺便证明一个定理:将若干个长度相同(设都为k)的字符串中的一个(设为e)分成两段,长度分别为l和|e|−l,那么如果我们已知字符串e在所有字符串的前l的字符构成的串中的排名和剩下来|e|−l个字符构成的串中的排名r1和r2,那么整一个字符串的排名等价于二元组(r1,r2)的排名。证明很容易,我们只需要注意到两个二元组(a,b)和(c,d)的比较规则是当a≠c时比较a和c,而当a=c时比较b和d,那么我们就能够看出,由于我们已经有了前面一段的排名,那么我们只需要比较排名,排名哪一个更前,哪一个的字典序就相应的越高;如果其前半段的排名相等,即字典序相等,那么其前半段是完全一样的,我们就可以忽略前半段,比较后半段的排名。仔细一想,我们就可以发现,上面给出的二元组的比较过程,和字典序的比较过程,是完全一样的!只不过,我们“已经比较”过某些东西了,所以不用再直接地比较两两间的字典序了。

利用倍增算法和快速排序的结合,我们可以在log2n×Θ(nlog2n)=Θ(nlog22n)的时间内解决这个问题,代码也很短,五六行左右就好了。如果没有时间写其他的话,可以直接用sort()和倍增算法相对暴力地解决。

但是,我们有更高效的算法。基数排序!!!

在这里,我们可以先按照上一轮的结果将第二个关键字排成一个序(相同的不管它),然后按照这个顺序,排。注意,插入的顺序应当和我们预先排的顺序一样。也就是说,大概是下面这样:

for (int i=0;i<n;++i){
insert(num1[i],i);
}


然后我们再将放进去的二元组再按照桶的大小顺序取出来,放好顺序,基数排序就完成了。

但是,光有构造远远不够,这样我们只能拥有两个数组(而且本质上相同)rank和sa。我们还需要一个特殊的工具,这个工具叫做最长公共前缀。

两个字符串S1和S2的最长公共前缀Lcp(S1,S2)的定义为Lcp(S1,S2)=maxi=1min{|S1|,|S2|}{S1i|S1i=S2i}

那么,我们可以很容易地发现,其实某两个后缀的最长公共前缀Lcp(Ssai,Ssaj)的值,实际上是mink=min{sai,saj}max{sai,saj}−1Lcp(Sk,Sk+1)。证明很容易,首先显然地,mink=min{sai,saj}max{sai,saj}−1Lcp(Sk,Sk+1)≤Lcp(Ssai,Ssaj)(相当于若干个公共前缀的公共部分,可能可以再长),另外若设p=mink=min{sai,saj}max{sai,saj}−1Lcp(Sk,Sk+1),q=Lcp(Ssai,Ssaj),而q>p,那么可以推出矛盾,这里不证(篇幅太长了,公式太大了……)

好吧,总之这个被证出来了,那么我们怎样快速地求出Lcp(Ssai,Ssai+1)呢?容易证明,Lcp(Ssai,Ssai+1)≥Lcp(Ssai,Ssai−1)−1

那么我们就可以不断地用上一个的答案来求出一个下界,再暴力扩展,然后继续求出下一个答案了。最后再用RMQ一搞就好了。

后缀数组的应用相当多,而且实现相对简洁(可能吧),只要多想想,几乎没什么是搞不定的……

【P.S.】后缀数组还可以有各种扩展,比如后缀家族的各种数据结构:后缀树、后缀数组、后缀自动机、后缀仙人掌……所以,太多了……
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐