后缀数组——用后缀处理字符串

我们知道AC自动机可以解决多模匹配问题。但是如果遇上在线多模匹配问题,AC自动机就不合适了,因为AC自动机必须将所有模式串建成一棵Trie树。这时,我们就可以利用后缀数组来解决。
后缀数组处理的是文本串。我们将文本串的每一条后缀拿出来,按照字典序排序,然后就可以处理后缀数组了。后缀数组sa[i]表示的就是排名第i位的后缀的第一个字符所在下标。这可能有点绕口,所以我们用样例解释一下,如对于文本串ABABA,则后缀为ABABA,BABA,ABA,BA,A,我们排序后就是:A,ABA,ABABA,BA,BABA,所以sa[1]=4(下标从0开始),表示排名为1的后缀(A)的下标为4;同理sa[2]=2,sa[3]=0,sa[4]=3,sa[5]=1。
既然知道了后缀数组是什么,我们就要开始构建后缀数组了,如果我们直接用sort来排序,时间复杂度会比较的高,所以我们考虑使用基数排序,但我们这里的基数排序并不一位一位的排,而可以使用倍增的算法。
比如对于文本串ABABA,我们先按第一位进行排序,则我们排序出来的数组是{1,2,1,2,1},然后我们将每一个二元组进行排序,每个二元组就是由上两个元素构成的,所以我们构成的二元组是{(1,2),(2,1),(1,2),(2,1),(1)}(最后的1没有数可以与它构成二元组,相当于它的第二位是空,ASCII最小),继续将二元组构建成数组,则排序出的数组是{2,3,2,3,1},然后我们要将4个一起匹配,将4个一起匹配时,我们就要取我们算出的排序数组里的x项和x+2项了。(想想为什么,可以根据下图理解),出来的二元组是(实际上使4个元素,但每一个元素又代表着一个二元组,所以组出来的是二元组){(2,2),(3,3),(2,1),(3),(1)},排序出的数组就是{3,5,2,4,1},到这里就可以将所有的顺序排除来了,然后我们按下标放入后缀数组里,出来的数组就是{4,2,0,3,1}正如上文所说。

这里写图片描述

在代码中我们可以使用滚动数组进行优化,因为有用的只有上一层和这层。
求后缀数组的代码:洛谷模板题
#include<bits/stdc++.h>
#define MAXN 2000005
using namespace std;
int read(){
    char c;int x;while(c=getchar(),c<'0'||c>'9');x=c-'0';
    while(c=getchar(),c>='0'&&c<='9') x=x*10+c-'0';return x;
}
string s;
int n,p[MAXN],c[MAXN],sa[MAXN],t[MAXN],t2[MAXN];
void build(int m){
    int *A=t,*B=t2;
    for(int i=0;i<m;i++) c[i]=0;
    for(int i=0;i<n;i++) c[A[i]=p[i]]++;
    for(int i=1;i<m;i++) c[i]+=c[i-1];
    for(int i=n-1;i>=0;i--) sa[--c[A[i]]]=i;
    for(int k=1;k<=n;k<<=1){
        int p=0;
        for(int i=n-k;i<n;i++) B[p++]=i;
        for(int i=0;i<n;i++) if(sa[i]>=k) B[p++]=sa[i]-k;
        for(int i=0;i<m;i++) c[i]=0;
        for(int i=0;i<n;i++) c[A[B[i]]]++;
        for(int i=1;i<m;i++) c[i]+=c[i-1];
        for(int i=n-1;i>=0;i--) sa[--c[A[B[i]]]]=B[i];
        swap(A,B);
        p=1;A[sa[0]]=0;
        for(int i=1;i<n;i++) A[sa[i]]=(B[sa[i-1]]==B[sa[i]]&&B[sa[i-1]+k]==B[sa[i]+k])?p-1:p++;
        if(p==n) break;m=p;
    }
}
int main()
{
    cin>>s;n=s.length();
    for(int i=0;i<n;i++){
        if(s[i]>='0'&&s[i]<='9') p[i]=s[i]-'0';
        if(s[i]>='A'&&s[i]<='Z') p[i]=s[i]-'A'+10;
        if(s[i]>='a'&&s[i]<='z') p[i]=s[i]-'a'+36;
    }
    build(62);
    for(int i=0;i<n;i++) printf("%d ",sa[i]+1);
    return 0;
}
有了后缀数组,我们就可以进行在线多模匹配,那么具体怎么匹配呢?
我们可以很简易的想到二分,因为我们后缀数组已经按照字典序排序,所以我们只要二分后缀数组,然后与分到的后缀的前缀进行比较即可。

猜你喜欢

转载自blog.csdn.net/stevensonson/article/details/79983587
今日推荐