后缀数组是跟着白书上面学的,感觉自己大概能理解了,写下来加深印象,以后慢慢学习。
所谓后缀就是从一个字符串某个位置开始到其末尾的字串(在KMP里也有这个概念)。包括了空串和该字符串本身。
后缀数组就是把所有的字符串后缀按照字典序排列后所得到的数组。在后缀数组的计算中,用到了以下几个关键数组。
rank用来记录字符串的排序,sa用来记录开头字符的位置,S用来记录字符串,S[i]表示第i个字符开头的后缀。
白书上的算法用的是倍增的思想。感觉和ST表很像。思想都是一样的。
因为如果你要直接对所有的后缀直接排序肯定超时。那么我们长度为n的字符串,能否通过已经处理过的长度为n/2的字符串排序呢?
每一次循环我们计算长度为k的字串的顺序,k每次乘以2.rank[i]就代表以第i个字符开头的长度为k的字串是第几小的。
如果我们想比较S[i,2*k]和S[j,2*k],分别代表以i和j开头的长度为2k的字串,那么我们是不是可以先比较,S[i,k]和S[j,k],如果相等,就比较S[i+k,k],S[j+k,k]。(因为是字典序比较,很好理解吧)
int n.k;
int rank[MAXN+1];
int tmp[MAXN+1];
//比较(rank[i],rank[i+k])和(rank[j],rank[j+k])
bool compare_sa(int i,int j){
if(rank[i]!=rank[j])
return rank[i]<rank[j];
else{
int ri=i+k<=n?rank[i+k]:-1;
int rj=j+k<=n?rank[j+k]:-1;
return ri<rj;
}
}
//rank用来记录字符串的排序,sa用来记录开头字符的位置,S用来记录字符串
//第一个通常是空字符串
//计算字符串S的后缀数组
void construct_sa(string S,int *sa){
n=S.lenth();
//初始长度为1,rank直接取字符的编码.
for(int i=0;i<=n;i++){
sa[i]=i;
rank[i]=i<n?S[i]:-1;
}
//利用对长度为k的排序的结果对长度为2k的排序
for(k=1;k<=n;k*=2){
sort(sa,sa+n+1,compare_sa);
//先在tmp中临时储存新计算的rank,再转存回rank中
tmp[sa[0]]=0;
for(int i=1;i<=n;i++){
tmp[sa[i]]=tmp[sa[i-1]]+(compare_sa(sa[i-1],sa[i])?1:0);
}
for(int i=0;i<=n;i++){
rank[i]=tmp[i];
}
}
}
这样我们在进行字符串匹配的时候,就可以用二分搜索的方法匹配字符串了。效率是O(|T|log|S|)
bool contain(string S,int *sa,string T){
int a=0;int b=S.lenth();
while(b-a>1){
int c=(a+b)/2;
//比较S从位置sa[c]开始长度为|T|的字串
if(S.compare(sa[c],T.lenth(),T)<0) a=c;
else b=c;
}
return S.compare(sa[b],T.lenth(),T)==0;
}
大概思路就是这样。具体看代码吧。