• 后缀数组入门(一)——后缀排序


    前言

    后缀数组这个东西早就有所耳闻,但由于很难,学了好几遍都没学会。

    最近花了挺长一段时间去研究了一下,总算是勉强学会了用倍增法来实现后缀排序(据说还有一种更快的(DC3)法,但是要难得多)。

    数组定义

    首先,为方便起见,我们用后缀(_i)表示从下标(i)开始的后缀。(相信大家都知道后缀是什么的)

    首先,我们需要定义几个数组:

    (s):需要进行后缀排序的字符串。

    (SA_i):记录排名为(i)的后缀的位置

    (rk_i):记录后缀(_i)的排名

    (pos_i):同样记录排名为(i)的后缀的位置

    (tot_i):用于基数排序,统计(i)的排名

    要注意理解这些数组的定义,这样才能明白后面的内容。

    第一次操作

    首先,让我们来一步步模拟一下第一次操作。

    我们第一步是要将每个后缀按照第(1)个字符进行排序。

    这应该还是比较简单的,不难发现可以初始化得到(rk_i=s_i,pos_i=i)

    然后我们对其进行第一次排序。

    注意,排序最好用(O(n))基数排序,用(sort)的话会多一个(log)

    具体的一些关于基数排序的细节可以见下。

    关于基数排序

    后缀排序中的基数排序,其实相当于将二元组((rk_i,pos_i))进行排序。

    首先,第一步自然是清空(tot)数组。

    然后,从(1)(n)枚举,将(tot_{rk_i})(1)

    接下来是一遍累加,求出每一个元素的排名。

    然后从(n)(1)倒序枚举,更新(SA)数组即可。

    接下来的操作

    接下来自然是要对每个后缀前(2)个字符进行排序了。

    暴力的方法就是再重新排序一遍。

    但实际上,在确定了第(1)个字符的大小关系后,我们就不需要如此麻烦了。

    因为后缀(_i)的第(2)个字符,实际上就是后缀(_{i+k})的第(1)个字符。

    因此我们通过第一次排序,就可以直接确定第(2)个字符的大小关系了。

    于是我们就可以重新用(pos)数组将这个大小关系记录下来,再次排序。

    然后就是按照这种方法来倍增处理第(4)个字符、第(8)个字符、第(16)个字符... ...

    重复此操作直至所有后缀各不相同即可。

    这样的总复杂度就是(O(nlogn))的了。

    具体实现还是有很多细节的,实在没理解的可以根据代码再研究一下。

    代码(板子题

    class Class_SuffixSort//后缀排序
    {
        private:
            int n,SA[N+5],rk[N+5],pos[N+5],tot[N+5];
            inline void RadixSort(int S)//基数排序,S表示字符集大小
            {
                register int i;
                for(i=0;i<=S;++i) tot[i]=0;//清空数组
                for(i=1;i<=n;++i) ++tot[rk[i]];//从1到n枚举,将tot[rk[i]]加1
                for(i=1;i<=S;++i) tot[i]+=tot[i-1];//累加
                for(i=n;i;--i) SA[tot[rk[pos[i]]]--]=pos[i];//倒序枚举,更新SA数组
            }
        public:
            inline void Solve(char *s)
            {
                register int i,k,cnt=0,Size=122;//初始化字符集大小为122(即'z'的ASCII码)
                for(n=strlen(s),i=1;i<=n;++i) rk[pos[i]=i]=s[i-1];//初始化rk数组和pos数组
                for(RadixSort(Size),k=1;cnt<n;Size=cnt,k<<=1)//先是一遍基数排序,然后倍增枚举k,直至所有后缀各不相同
                {
                    for(cnt=0,i=1;i<=k;++i) pos[++cnt]=n-k+i;//将长度小于等于k的后缀先加入数组中,此时的cnt相当于计数器
                    for(i=1;i<=n;++i) SA[i]>k&&(pos[++cnt]=SA[i]-k);//对于排名大于k的字符串,将其加入数组中
                    for(RadixSort(Size),i=1;i<=n;++i) pos[i]=rk[i];//基数排序一遍,然后将rk数组的值全部赋值给pos数组
                    for(rk[SA[1]]=cnt=1,i=2;i<=n;++i) rk[SA[i]]=(pos[SA[i-1]]^pos[SA[i]]||pos[SA[i-1]+k]^pos[SA[i]+k])?++cnt:cnt;//利用SA数组来得到rk,此时的cnt存储不同的字符串个数,从而得到排名
                }
                for(i=1;i<=n;++i) F.write(SA[i]),F.writec(' ');//输出答案
            }
    }S;
    
  • 相关阅读:
    Jquery弹出框插件大全
    RGB颜色在线转换
    正则表达式最后加一个/g或者/ig代表什么意思
    JS实现页面上链接新窗口打开
    防止网站服务器被黑的一些方法
    JS中字符串背后的秘密
    ASP.NET MVC 路由规则写法
    日积月累从细节做起
    VC++ 配置困惑
    父类子类指针函数调用注意事项
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/SuffixSort.html
Copyright © 2020-2023  润新知