【前言】
不要被文章的长度吓到,因为罗穗骞的论文要更长更详尽,我只是取了其中的一部分进行学习并做一个学习笔记,便于以后有需要的时候回顾。文章的内容主要是介绍后缀数组的实现,后缀数组的应用部分主要是结合例题来理解。
目录
【后缀数组】
【引入】
AC自动机可以解决多模板匹配问题,但前提是事先知道所有的模板。在实际应用中,很多时候都是无法事先知道查询的,这时需要预处理文本串T,而不是模式串。
假定文本串为BANANA,可以在它的末尾加一个字符$(代表一个没在串中出现过的字符),然后把它的所有后缀(BANANA$,ANANA$,NANA$,ANA$,NA$,A$)插入到一棵Trie中(前置知识:字典树详解),如下图所示:
有了这个后缀Trie,查找一个长度为m的模板时只需要进行一次普通Trie查找,时间复杂度为O(m)。
在实际运用中,会把后缀Trie中没有分支的链合并到一起,得到所谓的后缀树(suffix tree),但由于后缀树的构造算法比较复杂难懂且容易写错,因此我们就需要用到后缀数组。后缀数组是后缀树的替代品,具有容易编程实现,占用内存空间小,且能够实现后缀树的很多功能而时间复杂度也并不逊色的特点。
在绘制上图时,我们需要把所有后缀按字典序从小到大排序,也就是说,我们需要把每个结点的所有子结点排好序,字典序小的在左边(规定$比所有其他字符都小),然后每个叶结点里标上该后缀首字符的下标。比如后缀NA开始于BANANA的下标4(注意下标从0开始),那么NA对应的叶结点标有"4"。为了方便描述,我们把“以下标k开头的后缀”表示为Suffix(k),也就是说,对于文本串BANANA,Suffix(4)就是NA。
现在只需自左向右把所有叶子的编号排列出来,就可以得到后缀数组(suffix array)。
比如,BANANA的后缀数组SA[]={5,3,1,0,4,2}。
名次数组Rank[i]:保存的是Suffix(i)在所有后缀中从小到大排列的“名次”。
简单的说,后缀数组是“排第几的是谁?”,名次数组是“你排第几?”。容易看出,后缀数组和名次数组为互逆运算。
【后缀数组的实现】
根据定义,后缀数组可以直接通过一次快速排序得到,但是在最坏情况下,直接排序需要的时间是O(n^2logn)(虽然比较次数是O(nlogn),但是两个字符串的比较是O(n)的)。下面介绍后缀数组的两种实现方法:倍增算法和DC3算法。
【倍增算法】
主要思想:用倍增的方法对每个字符开始的长度为的子字符串进行排序,求出排名,即rank值。k从0开始,每次加1,当
大于n以后,每个字符开始的长度为
的子字符串便相当于所有的后缀。并且这些子字符串都一定已经比较出大小,即rank值中没有相同的值,那么此时的rank值就是最后的结果。每一次排序都利用上次长度为
的字符串的rank值,那么长度为
的字符串就可以用两个长度为
的字符串的排名作为关键字表示,然后进行基数排序,便得到了长度为
的字符串的rank值。以字符串“aabaaaab”为例,整个过程如下图所示。x、y表示长度为
的字符串的两个关键字。
【具体实现】
int wa[maxn],wb[maxn],wv[maxn],ws[maxn];
int cmp(int *r,int a,int b,int l)
{return r[a]==r[b]&&r[a+l]==r[b+l];}
void da(int *r,int *sa,int n,int m) //n为字符串的长度加1,因为在使用倍增算法前在原字符串后面加一个0
{
int i,j,p,*x=wa,*y=wb,*t;
for(i=0;i<m;i++) ws[i]=0;
for(i=0;i<n;i++) ws[x[i