在字符串處理當中,後綴樹和後綴數組都是很是有力的工具。ios
其中後綴樹你們瞭解得比較多,關於後綴數組則不多見於國內的資料。算法
其實後綴數組是後綴樹的一個很是精巧的替代品,它比後綴樹容易編程實現,編程
可以實現後綴樹的不少功能而時間複雜度也不太遜色,而且,它比後綴樹所佔用的空間小不少。數組
能夠說,在信息學競賽中後綴數組比後綴樹要更爲實用!ide
所以在本文中筆者想介紹一下後綴數組的基本概念、構造方法,工具
以及配合後綴數組的最長公共前綴數組的構造方法,最後結合一些例子談談後綴數組的應用。學習
學習後綴數組須要認識幾個概念:ui
子串this
字符串S的子串r[i..j],i<=j,表示S串中從i到j這一段,就是順次排列r[i],r[i+1],...,r[j]造成的子串。spa
後綴
後綴是指從某個位置 i 開始到整個串末尾結束的一個特殊子串。字符串r的從第i個字符開始的後綴表示爲Suffix(i),
也就是Suffix(i)=S[i...len(S)-1] 。
後綴數組(SA[i]存放排名第i大的後綴首字符下標)
後綴數組 SA 是一個一維數組,它保存1..n 的某個排列SA[1] ,SA[2] ,...,SA[n] ,
而且保證Suffix(SA[i])<Suffix(SA[i+1]), 1<=i<n 。
也就是將S的n個後綴從小到大進行排序以後把排好序的後綴的開頭位置順次放入SA 中。
名次數組(rank[i]存放suffix(i)的優先級)
名次數組 Rank[i] 保存的是 Suffix(i) 在全部後綴中從小到大排列的「名次」
注:這個是排序的關鍵字~(這句話是咱們排序的重點)
(個人理解):
sa[i]:保存的是S字符串的全部後綴在以字典序排序後,排在第i名的字符串在原來子串中的位置。
rank[i]:保存的是S字符串的全部後綴在以字典序排序後,原來的第i名如今排第幾。
簡單的說,後綴數組(SA)是「排第幾的是誰?」,名次數組(RANK)是「你排第幾?」。
容易看出,後綴數組和名次數組爲互逆運算。咱們只要算出了sa數組,就能夠在O(n)的時間複雜度內算出rank數組。
height數組:height[i]保存的是suffix(i)和suffix(i-1)的最長公共前綴的長度。也就是排名相鄰的兩個後綴的最長公共前綴。
要構造Suffix Array,主要就是構造sa數組,rank數組和height數組。
首先來看一下如何構造sa數組:
構造sa數組的方法有三種:
1)倍增算法:O(nlongn)
2)DC3算法:O(n)
3)skew算法(不經常使用)
這裏主要講一下DC3算法:
DC3算法是一個優秀的線性算法!
不少人都認爲DC3算法很複雜,其實也沒多複雜,代碼也就40多行,只是for循環多了點。
DC3算法:
1) 先將後綴分紅兩部分,而後對第一部分的後綴排序。
字符的編號從0開始。
將後綴分紅兩部分:
第一部分是後綴k(k模3不等於0)
第二部分是後綴k(k模3等於0)
2) 利用(1)的結果,對第二部分的後綴排序。
3) 將(1)和(2)的結果合併,即完成對全部後綴排序。
因而求出了全部後綴的排序,有什麼用呢?主要是用於求它們之間的最長公共前綴(Longest Common Prefix,LCP)。
求出sa數組以後,根據rank[sa[i]]=i,rank數組天然也就可以在O(n)的時間內求出。
那咱們如何快速的求出height數組呢?
令LCP(i,j)爲第i小的後綴和第j小的後綴(也就是Suffix(SA[i])和Suffix(SA[j]))的最長公共前綴的長度,則有以下兩個性質:
對任意i<=k<=j,有LCP(i,j) = min(LCP(i,k),LCP(k,j))
LCP(i,j)=min(i<k<=j)(LCP(k-1,k))
令height[i]=LCP(i-1,i),即height[i]表明第i小的後綴與第i-1小的後綴的LCP,則求LCP(i,j)就等於求height[i+1]~height[j]之間的RMQ,套用RMQ算法就能夠了,複雜度是預處理O(nlogn),查詢O(1).
這樣一來咱們就將height數組也求出來了。
下面用草稿紙來模擬一遍:
例如:
aabaaaab
總共有n=8個後綴:
1: aabaaaab
2: abaaaab
3: baaaab
4: aaaab
5: aaab
6: aab
7: ab
8: b
按照字典序排序後
sa[ 1 ] = 4 aaaab
sa[ 2 ] = 5 aaab
sa[ 3 ] = 6 aab
sa[ 4 ] = 1 aabaaaab
sa[ 5 ] = 7 ab
sa[ 6 ] = 2 abaaaab
sa[ 7 ] = 8 b
sa[ 8 ] = 3 baaaab
rank數組爲:
rank[1]=4
rank[2]=6
rank[3]=8
rank[4]=1
rank[5]=2
rank[6]=3
rank[7]=5
rank[8]=7
height數組爲:
height[ 1 ]=null
height[ 2 ]= 3
height[ 3 ]= 2
height[ 4 ]= 3
height[ 5 ]= 1
height[ 6 ]= 2
height[ 7 ]= 0
height[ 8 ]= 1
所以,全部子串的最長公共子串就是3.
這裏給出一個理解程序:
/* * this code is made by crazyacking * Verdict: Accepted * Submission Date: 2015-05-09-21.22 * Time: 0MS * Memory: 137KB */ #include <queue> #include <cstdio> #include <set> #include <string> #include <stack> #include <cmath> #include <climits> #include <map> #include <cstdlib> #include <iostream> #include <vector> #include <algorithm> #include <cstring> #define LL long long #define ULL unsigned long long using namespace std; const int MAXN=100010; //如下爲倍增算法求後綴數組 int wa[MAXN],wb[MAXN],wv[MAXN],Ws[MAXN]; int cmp(int *r,int a,int b,int l) {return r[a]==r[b]&&r[a+l]==r[b+l];} /**< 傳入參數:str,sa,len+1,ASCII_MAX+1 */ void da(const char *r,int *sa,int n,int m) { int i,j,p,*x=wa,*y=wb,*t; for(i=0; i<m; i++) Ws[i]=0; for(i=0; i<n; i++) Ws[x[i]=r[i]]++; for(i=1; i<m; i++) Ws[i]+=Ws[i-1]; for(i=n-1; i>=0; i--) sa[--Ws[x[i]]]=i; for(j=1,p=1; p<n; j*=2,m=p) { for(p=0,i=n-j; i<n; i++) y[p++]=i; for(i=0; i<n; i++) if(sa[i]>=j) y[p++]=sa[i]-j; for(i=0; i<n; i++) wv[i]=x[y[i]]; for(i=0; i<m; i++) Ws[i]=0; for(i=0; i<n; i++) Ws[wv[i]]++; for(i=1; i<m; i++) Ws[i]+=Ws[i-1]; for(i=n-1; i>=0; i--) sa[--Ws[wv[i]]]=y[i]; for(t=x,x=y,y=t,p=1,x[sa[0]]=0,i=1; i<n; i++) x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++; } return; } int sa[MAXN],Rank[MAXN],height[MAXN]; //求height數組 /**< str,sa,len */ void calheight(const char *r,int *sa,int n) { int i,j,k=0; for(i=1; i<=n; i++) Rank[sa[i]]=i; for(i=0; i<n; height[Rank[i++]]=k) for(k?k--:0,j=sa[Rank[i]-1]; r[i+k]==r[j+k]; k++); // Unified for(int i=n;i>=1;--i) ++sa[i],Rank[i]=Rank[i-1]; } char str[MAXN]; int main() { while(scanf("%s",str)!=EOF) { int len=strlen(str); da(str,sa,len+1,130); calheight(str,sa,len); puts("--------------All Suffix--------------"); for(int i=1; i<=len; ++i) { printf("%d:\t",i); for(int j=i-1; j<len; ++j) printf("%c",str[j]); puts(""); } puts(""); puts("-------------After sort---------------"); for(int i=1; i<=len; ++i) { printf("sa[%2d ] = %2d\t",i,sa[i]); for(int j=sa[i]-1; j<len; ++j) printf("%c",str[j]); puts(""); } puts(""); puts("---------------Height-----------------"); for(int i=1; i<=len; ++i) printf("height[%2d ]=%2d \n",i,height[i]); puts(""); puts("----------------Rank------------------"); for(int i=1; i<=len; ++i) printf("Rank[%2d ] = %2d\n",i,Rank[i]); puts("------------------END-----------------"); } return 0; }
這裏只是簡單的介紹幾種後綴數組的運用,真正的熟練後綴數組,還須要經過不斷的作題、不斷的實踐來掌握。
最長公共子串
咱們知道,字符串的任何一個子串均可以看做是這個字符串某個的後綴的前綴。
求A和B的最長公共子串等價於求A的後綴和B的後綴的最長公共前綴的最大值。
將第二個字符串寫在第一個字符串的後面,中間用一個沒有出現過的字符隔開,在求出這個新字符串的後綴數組,而後咱們只須要找最大的height[i]就可(前提是要判斷是否不在同一個字符串中)。
單個字符串的相關問題
兩個字符串的相關問題
多個字符串的相關問題