「后缀数组」学习笔记

后缀数组的构造

注:字符串下表从1开始。

后缀数组$sa$定义

$sa_i$表示字典序排名为$i$的后缀的起始位置。

思想

暴力使用快速排序对$n$个后缀进行排序的比较次数是$n \log n$,然而比较一次的时间复杂度为$O(n)$。导致总复杂度为$O(n^2 \log n)$。

$Manber$ & $Mayer$倍增法,基于每一次对每个后缀的前$2^k$个元素进行基数排序,复杂度$O(n \log n)$。其思想的中心在于,由于后缀与后缀之间不像普通的数字,我们要排序的对象本身是内部有联系的。每一次排完后缀的前$2^k$个元素之后要想排$2^{k+1}$个,不过是由两个$2^k$拼合而成的。而由于已对所有$2^k$进行了排序,也就知道了$2^k$的相对关系,这样也就将问题转化为了对二元组的排序。而这能够利用基数排序$O(n)$完成。

基数排序的思想就是利用第二关键字已经有序的情况下,对第一关键字排序。这样保证了对于每一个第一关键字,其第二关键字都是有序的。因此整个就排好序了。

/*DennyQi 2019*/
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <queue>
using namespace std;
const int N = 1000010;
inline int read(){
    int x(0),w(1); char c = getchar();
    while(c^'-' && (c<'0' || c>'9')) c = getchar();
    if(c=='-') w = -1, c = getchar();
    while(c>='0' && c<='9') x = (x<<3)+(x<<1)+c-'0', c = getchar(); 
    return x*w;
}
char s[N];
int n,x[N],y[N],c[N],sa[N];
inline void BuildSA(int m){
    int p;
    for(int i = 1; i <= n; ++i) ++c[x[i] = s[i]];
    for(int i = 1; i <= m; ++i) c[i] += c[i-1];
    for(int i = n; i >= 1; --i) sa[c[x[i]]--] = i;
    for(int k = 1; k <= n; k += k){
        p = 0;
        for(int i = n-k+1; i <= n; ++i) y[++p] = i;
        for(int i = 1; i <= n; ++i) if(sa[i] > k) y[++p] = sa[i]-k;
        for(int i = 1; i <= m; ++i) c[i] = 0;
        for(int i = 1; i <= n; ++i) ++c[x[i]];
        for(int i = 1; i <= m; ++i) c[i] += c[i-1];
        for(int i = n; i >= 1; --i) sa[c[x[y[i]]]--] = y[i], y[i] = 0;
        swap(x,y);
        x[sa[1]] = 1, p = 1;
        for(int i = 2; i <= n; ++i) x[sa[i]] = (y[sa[i]]==y[sa[i-1]] && y[sa[i]+k]==y[sa[i-1]+k]) ? p : ++p;
        if(p >= n) break;
        m = p;
    }
}
int main(){
    // freopen(".in","r",stdin);
    scanf("%s",s+1);
    n = strlen(s+1), 
    BuildSA('z'+1);
    for(int i = 1; i <= n; ++i) printf("%d ",sa[i]);
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/qixingzhi/p/11015673.html