后缀数组

下面的例子取自刘汝佳大白书。

后缀数组是一个强力的字符串处理工具。

首先定义后缀i是以下标i为首到字符串末的子字符串。

而后缀数组sa定义为以字典顺序记录后缀。

所以,字符串AABAAAAB的后缀数组sa={3,4,5,0,6,1,7,2}。

仅仅只有后缀数组还不够,还需要构造一个名次数组ra。

名次数组ra定义为对应后缀的名次,上述字符串的名次数组ra={3,5,7,0,1,2,4,6}。

除此以外,还有一个height数组,height[i]定义为后缀sa[i-1]与后缀sa[i]的公共前缀。

上述字符串的height={0,3,2,3,1,2,0,1}。

文字描述可能比较难懂,通过图画就容易了。

                                                            

图中还提供了如何使用height数组求两个后缀的最长公共前缀。即对height数组进行RMQ(ra[i]+1,ra[j])。

下面讲述如何求这三个数组。首先求sa数组。

使用倍增算法,使用基数排序。

首先对字母分配一个关键字,如AABAAAAB,则可以按照顺序为每个字母分配{1,1,2,1,1,1,1,2}。

然后对字母进行组合,把两个字符组合进行排序。则有AA,AB,BA,AA,AA,AA,AB,B#(#表示空字符,空字符分配为0)。

然后再对这些字符串分配关键字为{1,2,4,1,1,1,2,3},依次类推在两两组合,显然基数排序时间复杂度为O(n),总共logn个循环,总时间复杂度O(nlogn)。


ra数组的计算,可以证明ra[sa[i]]=i。


height数组的计算:

使用一个辅助数组h[i]=height[ra[i]]。该辅助数组有一个性质h[i]>=h[i-1]-1。

性质证明:设后缀i-1的前一个为后缀k,已经求出h[i-1]=height[ra[i-1]]。

分别删除后缀k和后缀i-1的首字符,得到后缀k+1和后缀i,显然h[i]>=h[i-1]-1。

所以可以使用一个循环计算height数组。

代码如下:

char ch[maxn];
int sa[maxn];
int ra[maxn];
int height[maxn];
int t1[maxn];//辅助数组
int t2[maxn];//辅助数组
int c[maxn];//辅助数组,基数排序的容器

void init(int m,int n)//m表示关键字的取值范围,n为字符串的长度
{
    for(int i=n;i<n*2;i++)t1[i]=-1,t2[i]=-1;//模板用时发现问题,是数组用时没有初始化
    int i,*x=t1,*y=t2;
    //首先对一个字母的子串排序
    for(i=0;i<m;i++)c[i]=0;//容器清零
    for(i=0;i<n;i++)c[x[i]=ch[i]-'A']++;//计算对应关键字容器的元素个数
    for(i=1;i<m;i++)c[i]+=c[i-1];//基数排序中计算容器下标
    for(i=n-1;i>=0;i--)sa[--c[x[i]]]=i;//获得对应sa数组
    for(int k=1;k<=n;k<<=1)
    {
        int p=0;
        //使用辅助数组对第二关键字排序
        for(i=n-k;i<n;i++)y[p++]=i;//k以后的后缀合并时必定与一个空字符串作为第二关键字
        for(i=0;i<n;i++)if(sa[i]>=k)y[p++]=sa[i]-k;//按第二关键字顺序把第一关键字下标放入辅助数组
        
        //最后y数组按第二关键字记录着第一关键字的下标
        for(i=0;i<m;i++)c[i]=0;//容器清零
        for(i=0;i<n;i++)c[x[i]]++;//计算对应关键字容器的元素个数
        for(i=1;i<m;i++)c[i]+=c[i-1];//基数排序中计算容器下标
        for(i=n-1;i>=0;i--)sa[--c[x[y[i]]]]=y[i];//计算sa数组

        swap(x,y);//交换辅助数组
        p=1;x[sa[0]]=0;
        for(i=1;i<n;i++)
            x[sa[i]]=y[sa[i-1]]==y[sa[i]]&&y[sa[i-1]+k]==y[sa[i]+k]?p-1:p++;//重新计算关键字,可以证明最后k个关键字一定不相同
        if(p>=n)break;//当关键字数量大于等于n时结束排序
        m=p;
    }
    //计算ra数组和height数组
    for(i=0;i<n;i++)
        ra[sa[i]]=i;
    int k=0;
    int j;
    height[0]=0;
    for(i=0;i<n;i++)
    {
        if(k)k--;
        if(ra[i]-1<0){k=0;continue;}
        j=sa[ra[i]-1];
        while(i+k<n&&j+k<n&&ch[i+k]==ch[j+k])k++;
        height[ra[i]]=k;
    }
}

上述代码中,sa[--c[x[y[i]]]]=y[i]比较难理解。

首先y数组按顺序记录第一关键字下标,所以反过来使用则按逆顺序。

而x数组记录对应子串的关键字,则使用x[y[i]]可以时第二关键字大的靠后的下标。(x[y[i]]为第二关键字排序后的数组)


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值