後綴數組(一堆乾貨)

其實就是將兩篇論文裏的東西整合在了一塊兒,而且提供了一個比較好理解的板。ios

 

後綴數組

 

        字符串:一個字符串S是將n個字符順次排列造成的數組,n稱爲S的長度,表示爲len(S)。S的第i個字符表示爲S[i]。算法

        子串:字符串S的子串S[i…j],i<=j,表示從S串中從i到j這一段,也就是順次排列S[i],S[i+1],……,S[j]造成的字符串。數組

        後綴:後綴是指從某個位置i開始到整個字符串末尾結束的一個特殊子串。字符串S的從i開關的後綴表示爲Suffix(S,i),也就是Suffix(S,i)=S[i…len(S)]。數據結構

 

        關於字符串的大小比較,是指一般所說的「字典順序」比較,也就是對於兩個字符串u、v,令i從1開始順次比較u[i]和v[i],若是u[i]=v[i]則令i加1,不然若u[i]<v[i]則認爲u<v,u[i]>v[i]則認爲u>v(也就是v<u),比較結束。若是i>len(u)或者i>len(v)仍未比較結果,那麼若len(u)<len(v)則認爲u<v,若len(u)=len(v)則認爲u=v,若len(u)>len(v)則u>v。函數

        從字符串的大小比較定義來看,S的開頭兩個位置後綴u和v進行比較的結果不多是相等,由於u=v的必要條件len(u)=len(v)是不可能知足的。工具

        對於約定的字符串S,從位置i開頭的後綴直接寫成Suffix(i),省去參數S。spa

 

        後綴數組:後綴數組SA是一個一維數組,它保存1..n的某個排列SA[1],SA[2],……,SA[n],並保證Suffix(SA[i])<Suffix(SA[i+1]),1<=i<n。也就是將S的n個後綴從小到大進行排序以後把排好序的後綴的開頭位置順次放入SA中。.net

        名次數組:名次數組Rank[i]表示在字符串S中以i開始後綴在全部的後綴中排名第幾。code

        簡單地說,後綴數組是「排第幾的是誰?」,名次數組是「你排第幾?」。容易看出後綴數組和名次數組互爲逆運算。blog

 

下面僅介紹倍增法構造後綴數組

         最直接的方法,固然是把S的後綴都看做一些普通的字符串,按照通常字符串排序的方法對它們從小到大進行排序。

         不難看出這樣的方法是很笨拙的,由於它沒有利用各個後綴之間的有機聯繫,因此它的效率不高。倍增算法正是充分利用了各個後綴之間的聯繫,將構造後綴數組的數組的最壞時間複雜度成功降到O(nlogn)。

         對於一個字符串u,咱們定義u的k-前綴

         定義k-前綴比較關係<k、=k和<=k

         設兩個字符串u和v,

                u<kv 當且僅當uk<vk

                u=kv 當且僅當uk=vk

                u≤kv 當且僅當uk≤vk

        直觀地看這些加了一個下標k的比較符號的意義就是對兩個字符串的前k個字符進行字典序比較,特別的一點就是在做大於和小於的比較時若是某個字符串的長度達不到k也沒有關係,只要可以在k個字符比較結束以前獲得第一個字符串小於第二個字符串就能夠了。

        根據前綴比較符的性質咱們能夠獲得如下的很是重要的性質:

                性質1.1 對於k>n,Suffix(i)<kSuffix(j)等價於 Suffix(i)<Suffix(j)。

                性質1.2 Suffix(i)=2kSuffix(j)等價於

                        Suffix(i)=kSuffix(j)且Suffix(i+k)=kSuffix(j+k)。

                性質1.3 Suffix(i)<2kSuffix(j)等價於

                        Suffix(i)<kSuffix(j)或(Suffix(i)=kSuffix(j)且Suffix(i+k)<kSuffix(j+k))。

        這裏有一個問題,當i+k>n或者j+k>n的時候Suffix(i+k)或者Suffix(j+k)是無明肯定義的表達式,因此咱們在字符串S的末尾添加一個’$’特殊符號,讓它比字符串中全部的字符都小,這樣便可以知足字典序比較的要求,也能不越界。

 

        基於上面的結論,倍增算法的主要思路是:用倍增的方法對每一個字符開始的長度爲2^k的子字符串進行排序,求出排名,即rank值。k從0開始,每次加1,當2^k大於n之後,每一個字符開始的長度爲2^k的子字符串便至關於全部的後綴。而且這些子字符串必定已經比較出大小,即Rank值中沒有相同的值,那麼此時的Rank值就是最後的結果。每一次排序都利用上次長度爲2^k-1的字符串Rank值,那麼長度爲2^k的字符串就能夠用兩個長度爲2^k-1的字符串的排名做爲關鍵字表示,而後進行基數排序,便得出了長度爲2^k的字符串的rank值。以字符串「aabaaaab」爲例,整個過程如圖2所示。其中x、y是表示長度爲2^k的字符串的兩個關鍵字。第一趟排序x是直接將字符串轉化成數字,y直接賦值爲0。

最長公共前綴

         如今一個字符串S的後綴數組SA能夠在O(nlogn)的時候內計算出來。利用SA咱們已經能夠作不少事情,好比在O(mlogn)的時間內進行模式匹配,其中m,n分別爲模式串和待匹配串的長度。可是要想更充分地發揮後綴數組的威力,咱們還須要計算一個輔助的工具――最長公共前綴(Longest Common Prefix)。

         對於兩個字符串u,v定義函數lcp(u,v)=max(i|u=iv),也就是從頭開始順次比較u和v的對應字符,對應字符持續相等的最大位置,稱爲這兩個字符串的最長公共前綴。

         對正整數i,j定義LCP(i,j)=lcp(Suffix(SA[i]),Suffix(SA[j]),其中i,j均爲1到n的整數。LCPi(I,j)也就是後綴數組中第i個和第j個後綴的最長公共前綴的長度。

         關於LCP有兩個顯而易見的性質:

                  性質1     LCP(i,j)=LCP(j,i)

                  性質2      LCP(i,i)=len(Suffix(SA[i]))=n-SA[i]+1

         這兩個性質的用處在於,咱們計算LCP(i,j)時只須要考慮i<j的狀況,由於i>j時可交換I,j,i=j時能夠直接輸出結果n-SA[i]+1。

         直接根據定義,用順次比較對應字符的方法來計算LCP(I,j)顯然是很低效的,時間複雜度爲O(n),因此咱們必須進行適當的預處理以下降每次計算LCP的複雜度。

         通過仔細的分析,咱們發現LCP函數有一個很是好的性質:

                   設i<j,則LCP(i,j)=min{LCP(k-1,k)|i+1<=k<=j}         (LCP Theorem)  

         要證實LCPTheorem,首先證實 LCP Lemma:

                   對任意1<=i<j<k<=n,LCP(I,k)=min{LCP(I,j),LCP(j,k)}

         證實:設p=min{LCP(I,j),LCP(j,k)},則有LCP(I,j)>=p,LCP(j,k)>=p。

                   設Suffix(SA[i])=u,Suffix(SA[j])=v,Suffix(SA[k])=w。

                   由u=LCP(I,j)v得u=pv;同理v=pw。

                   因而Suffix(SA[i])=pSuffix(SA[k]),即LCP(I,k)>=p。         (1)

                   又設LCP(I,k)=q>p,則

                   U[1]=w[1],u[2]=w[2],…,u[q]=w[q]。

                   而min{LCP(I,j),LCP(j,k)}=p,說明u[p+1]!=v[p+1]或v[p+1]!=w[q+1]

                   設u[p+1]=x,v[p+1]=y,w[p+1]=z,顯然爲x<=y<=z,又由p<q得p+1<q,應該有x=z,也就是x=y=z,這與u[p+1]!=v[p+1]或v[p+1]!=w[q+1]矛盾。

                   因而,q>p不成立,即LCP(I,k)<=p。                              (2)

                   綜合(1)(2)知LCP(I,k)=p=min{LCP(I,j),LCP(j,k)},LCP Lemma得證。

         根據數學概括法可證得,LCPTheorem成立。

 

         定義一維數組height,令;height[i]=LCP(i-1,i),1<i<=n,並設height[1]=0。

         由LCP Theorem,LCP(I,j)=min{height[k]|i+1<=k<=j},也就是說,計算LCP(I,j)等同於詢問一維數組height中下標在i+1到j範圍內的全部元素的最小值。若是height數組是固定的,這就是很是經典的RMQ(Range Minimum Query)問題。

         對於一個固定字符串S,其height數組顯然是固定的,只要咱們能高效地求出height數組,那麼運用RMQ方法進行預處理以後,每次計算LCP(I,j)的時間複雜度就是常數級了。因而只有一個問題了――若是儘可能高效地算出height數組。

         根據計算後綴數組的經驗,咱們不該該把n個後綴當作互不相關的普通字符串,而應該儘可能利用它們之間的聯繫,下面證實一個很是有用的性質:

         爲了描述方便,設h[i]=height[Rank[i]],即height[i]=h[SA[i]]。h數組知足一個性質:

         性質3      對於i>1且Rank[i]>1,必定有h[i]>=h[i-1]-1。

         爲了證實性質3,咱們有必要明確兩個事實:

         設i<n,j<n,Suffix(i)和Suffix(j)知足lcp(Suffix(i),Suffix(j))>1,則如下兩點成立:

         Fact1 Suffix(i)<Suffix(j) 等價於 ;Suffix(i+1)<Suffix(j+1)。

         Fact2 必定有lcp(Suffix(i+1),Suffix(j+1))=lcp(Suffix(i),Suffix(j))-1。

         看起來很神奇,但其實很天然:lcp(Suffix(i),Suffix(j))>1說明Suffix(i)和Suffix(j)的第一個字符是相同的,設它爲a,則Suffix(i)至關於a後鏈接Suffix(i+1),Suffix(j)至關於a後鏈接Suffix(j+1)。比較Suffix(i)和Suffix(j)時,第一個字符必定是相等的,因而後面就等價比較Suffix(i)和Suffix(j),所以Fact 1成立。Fact 2可相似證實。

 

         因而能夠證實性質3:

         當h[i-1]<=1時,結論顯然成立,因h[i]>=0>=h[i-1]-1。

         當h[i-1]>1時,也即height[Rank[i-1]]>1,可見Rank[i-1]>1,因height[1]=0。

         令j=i-1,k=SA[Rank[j]-1]。顯然有Suffix(k)<Suffix(j)。

         根據h[i-1]=lcp(Suffix(k),Suffix(j))>1和Suffix(k)<Suffix(j)。

         由Fact 2 知lcp(Suffix(k+1),Suffix(i))=h[i-1]-1。

         由Fact 1 知Rank[k+1]<Rank[i],也就是Rank[k+1]<=Rank[i]-1。

         因而根據LCPCorollary,有

         LCP(Rank[i]-1,Rank[i])>=LCP(Rank[k+1],Rank[i])

                                               =lcp(Suffix(k+1),Suffix(i))

                                               =h[i-1]-1

         因爲h[i]=height[Rank[i]]=LCP(Rank[i]-1,Rank[i]),最終獲得h[i]>=h[i-1]-1。

        

         根據性質3,能夠令i從1循環到n按照以下方法依次算了h[i]:

         若Rank[i]=1,則h[i]=0。字符比較次數爲0.

         若i=1或者h[i-1]<=1,則直接將Suffix(i)和Suffix(Rank[i]-1)從第一個字符開始依次比較直到有字符不一樣,由此算出h[i]。字符比較次數爲h[i]+1,不超過h[i]-h[i-1]+2。

         不然,說明i>1,Rank[i]>1,h[i-1]>1,根據性質3,Suffix(i)和Suffix(Rank[i]-1)至少有前h[i-1]-1個字符是相同的,因而字符比較能夠從h[i-1]開始,直到某個字符不相同,由此計算出h[i]。字符比較次數爲h[i]-h[i-1]+2。

 1 #include <iostream>
 2 #include <cstdio>
 3 #include <cstring>
 4 using namespace std;
 5 const int N=21000;
 6 struct SuffixArray
 7 {
 8     int n,len,A[N],C[N],sa[N],rank[N],height[N];
 9     struct RadixEle
10     {
11         int id,k[2];
12         RadixEle(){}
13         RadixEle(int a,int b,int c){id=a;k[0]=b;k[1]=c;}
14     }RT[N],RE[N];
15     void RadixSort()
16     {
17         for(int y=1;y>=0;y--)
18         {
19             memset(C,0,sizeof(C));
20             for(int i=1;i<=n;i++) C[RE[i].k[y]]++;
21             for(int i=1;i<N;i++) C[i]+=C[i-1];
22             for(int i=n;i>=1;i--) RT[C[RE[i].k[y]]--]=RE[i];
23             for(int i=1;i<=n;i++) RE[i]=RT[i];
24         }
25         for(int i=1;i<=n;i++)
26         {
27             rank[RE[i].id]=rank[RE[i-1].id];
28             if(RE[i].k[0]!=RE[i-1].k[0]||RE[i].k[1]!=RE[i-1].k[1])
29                 rank[RE[i].id]++;
30         }
31     }
32     void CalcSA()
33     {
34         for(int i=1;i<=n;i++) RE[i]=RadixEle(i,A[i],0);
35         RadixSort();
36         for(int k=1;1+k<=n;k*=2)
37         {
38             for(int i=1;i<=n;i++) 
39                 RE[i]=RadixEle(i,rank[i],(i+k<=n?rank[i+k]:0));
40             RadixSort();
41         }
42         for(int i=1;i<=n;i++) sa[rank[i]]=i;
43     }
44     void CalcHeight()
45     {
46         int h=0;
47         for(int i=1;i<=n;i++)
48         {
49             if(rank[i]==1) h=0;
50             else
51             {
52                 int k=sa[rank[i]-1];
53                 if(--h<0) h=0;
54                 while(A[i+h]==A[k+h]) h++;
55             }
56             height[rank[i]]=h;
57         }
58     }
59 }SA;
相關文章
相關標籤/搜索