1. 程式人生 > 實用技巧 >字尾陣列

字尾陣列

用倍增思想求sa_k,rk_k -> sa_2k,rk_2k
用基數排序,排序二元組(rk[i],rk[i+k])

#include<bits/stdc++.h>
const int N=1000010;
char s[N];
int n,m,num;
int sa[N],rk[N],bac[N],y[N],tmp[N];//sa[i]陣列表示字尾排名為i的位置,rk[i]表示字尾[i..n]的排名
void getsa(){
    //rk[i]表示位置i的第一關鍵字(排名)
    //二元組(rk[i],rk[i+k])
    //初始化基數排序(單字元,其實是單元)
    for(int i=1;i<=n;i++)bac[rk[i]=s[i]]++;//bac[i]表示第一關鍵字小於i的個數
    for(int i=2;i<=m;i++)bac[i]+=bac[i-1];//故就先逐個統計,再求字首和,相當於用桶計數
    for(int i=n;i>=1;i--)sa[bac[rk[i]]--]=i;//在第二關鍵字有序的情況下,對位置按照第一關鍵字的bac陣列,逐個附排名 
    for(int k=1;k<=n;k<<=1){
        num=0;//排名的數量,初始化為單個字元的ASCLL碼上限,m=ascll('z')=122
        //y[i]表示第二關鍵字排名為i的二元組的第一關鍵字位置(i)
        for(int i=n-k+1;i<=n;i++)y[++num]=i;//沒有第二關鍵字的排名最靠前
        for(int i=1;i<=n;i++)if(sa[i]>k)y[++num]=sa[i]-k;//利用已有的sa_(k/2)陣列,rk靠前的且有第一關鍵字的,將第一關鍵字位置sa_(k/2)[i]-k放入y
        //在上一輪中,rk_k陣列已經求好了,倍增求解,
        //接下來利用基數排序,求出sa_k
        for(int i=1;i<=m;i++)bac[i]=0;
        for(int i=1;i<=n;i++)bac[rk[i]]++;//bac[i]表示第一關鍵字小於i的個數
        for(int i=2;i<=m;i++)bac[i]+=bac[i-1];//故就先逐個統計,再求字首和,相當於用桶計數
        for(int i=n;i>=1;i--)sa[bac[rk[y[i]]]--]=y[i];//在第二關鍵字有序的情況下,對位置按照第一關鍵字的bac陣列,逐個附排名 
        memcpy(tmp,rk,sizeof(tmp));//用tmp存一下rk_(k/2),在求rk_k時仍會用到
        //其實就是利用sa_k和rk_(k/2)陣列求rk_k
        rk[sa[1]]=1;num=1;
        for(int i=2;i<=n;i++){
            if(tmp[sa[i]]==tmp[sa[i-1]]&&tmp[sa[i]+k]==tmp[sa[i-1]+k])rk[sa[i]]=num;
            else rk[sa[i]]=++num;
        }
        if(num==n)break;//已經有了n種排名
        m=num;//m表示排名的數量(在桶排中為值域)
    }
}
int main(){
    scanf("%s",s+1);n=strlen(s+1);m=122;
    getsa();for(int i=1;i<=n;i++)printf("%d ",sa[i]);
    return 0;
}

無註釋版:

#include<bits/stdc++.h>
const int N=1000010;
char s[N];
int n,m,num;
int sa[N],rk[N],bac[N],y[N],tmp[N];
void getsa(){
    for(int i=1;i<=n;i++)bac[rk[i]=s[i]]++;
    for(int i=2;i<=m;i++)bac[i]+=bac[i-1];
    for(int i=n;i>=1;i--)sa[bac[rk[i]]--]=i;
    for(int k=1;k<=n;k<<=1){
        num=0;
        for(int i=n-k+1;i<=n;i++)y[++num]=i;
        for(int i=1;i<=n;i++)if(sa[i]>k)y[++num]=sa[i]-k;
        for(int i=1;i<=m;i++)bac[i]=0;
        for(int i=1;i<=n;i++)bac[rk[i]]++;
        for(int i=2;i<=m;i++)bac[i]+=bac[i-1];
        for(int i=n;i>=1;i--)sa[bac[rk[y[i]]]--]=y[i];
        memcpy(tmp,rk,sizeof(tmp));
        rk[sa[1]]=1;num=1;
        for(int i=2;i<=n;i++){
            if(tmp[sa[i]]==tmp[sa[i-1]]&&tmp[sa[i]+k]==tmp[sa[i-1]+k])rk[sa[i]]=num;
            else rk[sa[i]]=++num;
        }
        if(num==n)break;
        m=num;
    }
}
int main(){
    scanf("%s",s+1);n=strlen(s+1);m=122;
    getsa();for(int i=1;i<=n;i++)printf("%d ",sa[i]);
    return 0;
}