后缀数组模板

以下代码来自 Leo_h ,加了一点注释,稍微改了一点地方。

//from leo_h
#include<cstdio>  
#include<iostream>  
#include<algorithm>  
#include<cstring>  
#include<vector>  
using namespace std;  
#define maxn 100005  
#define rank RANK  
int n;
char str[maxn];
int cnt[maxn];
int t1[maxn],t2[maxn];
int sa[maxn];  //sa[i]表示排名为i的后缀 
int rank[maxn];//rank[i]表示后缀i的排名 
int height[maxn];
void build()
{
    int *x=t1,*y=t2;//x,y为两个临时数组 
    int m=26;//下一次编号的总个数 

    //按照第一个字符的大小把后缀安排到后缀数组中 :
    for(int i=0;i<n;i++) cnt[x[i]=(str[i]-'a')]++;
    //统计每种字母出现的次数 

    for(int i=1;i<m;i++) cnt[i]+=cnt[i-1];
    //为每种字母出现次数做前缀和 

    for(int i=n-1;i>=0;i--) sa[--cnt[x[i]]]=i;
    //这样的做法保证了相同的字母被放到了连续的一块
    //原理类似于计数排序(只有一位的基数排序) 

    for(int k=1;k<=n;k<<=1)//考虑每一个后缀的前k位 
    {
        //x为上一次的编号,y为这一次的序列 

        int p=0;//当前序列长度

        //利用sa排序第二关键字 
        for(int i=n-1;i>=n-k;i--) y[p++]=i;
        //后k个后缀没有第二关键字(或称第二关键字为0),直接放到最前面就好了 

        for(int i=0;i<n;i++) if(sa[i]>=k) y[p++]=sa[i]-k;  
        //y[i]:position of the suffix having the i-th "next k num",similar to sa
        //把长度大于等于k的后缀,按照sa[i]中的原顺序放到y里 

        //现在我们按照第一关键字再排序 

        memset(cnt,0,sizeof(cnt));
        for(int i=0;i<p;i++) cnt[x[y[i]]]++;
        //统计每一种编号出现的次数 

        for(int i=1;i<m;i++) cnt[i]+=cnt[i-1];
        //为每种编号的出现次数计算前缀和 

        for(int i=p-1;i>=0;i--) sa[--cnt[x[y[i]]]]=y[i];  
        //give a higher rank to suffix with larger second keyword when having same first one 
        //按照第一关键字排序, 基数排序 

        swap(x,y);//this time,y stores the information  
        //y为上一次的编号,x为这一次的序列,我们打算把x直接覆盖掉

        p=0;//统计不同的编号的出现次数,
            //编号从0开始所以当前的最后一个编号为p-1 

        for(int i=0;i<n;i++)//计算下一次的编号,并存入x中 
            x[sa[i]]= (i==0||y[sa[i]]!=y[sa[i-1]]||y[sa[i]+k]!=y[sa[i-1]+k]) ? p++:p-1;
            //考虑sa中两个相邻的位置的编号,
            //要么相同,要么后者是前者+1

            //不妨令 sa[-1] 的编号为 -1
            //当: i == 0 或 上一次的两个权位中有任意一个不同 时,新开一个编号p
            // sa[0] 的编号为 sa[-1]的编号+1

        if(p>=n) break;//所有编号都不同,排序完成
        m=p;//下一次的编号总个数为p 
    }
    for(int i=0;i<n;i++) rank[sa[i]]=i;//rank 是 sa 的反函数 
    int k=0;
    for(int i=0;i<n;i++)//构造height数组 
    {
        if(k) k--;//重要的一条性质 height[rank[i]] >= height[rank[i-1]]-1
        if(rank[i]==0) continue;//height[0]不存在 
        int j=sa[rank[i]-1];//后缀j 为 后缀i在sa中的 上一个后缀 
        while(str[i+k]==str[j+k]) k++;//向后检查直到不匹配为止 
        height[rank[i]]=k;//第一个不匹配位置的下标就是匹配的长度 
    }

}
int main()  
{  
    //freopen(".in","r",stdin);  
    //scanf("%d",&n);  
    scanf("%s",str);
    n=strlen(str);  
    build();  
    for(int i=0;i<n;i++) printf("%4d ",sa[i]);
    putchar('\n');  
    for(int i=0;i<n;i++) printf("%4d ",rank[i]);  
    putchar('\n');  
    for(int i=0;i<n;i++) printf("%4d ",height[i]);
    return 0;
}  
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值