后綴數組入門(一)——后綴排序


前言

后綴數組這個東西早就有所耳聞,但由於很難,學了好幾遍都沒學會。

最近花了挺長一段時間去研究了一下,總算是勉強學會了用倍增法來實現后綴排序(據說還有一種更快的\(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;


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM