這篇博客不打算講多麼詳細,網上關於後綴數組的blog比我講的好多了,這一篇博客我是爲本身加深印象寫的。html
給大家分享了那麼多,容我自私一回吧~算法
參考資料:這位dalao的blog數組
1、關於求SuffixArray的一些變量定義:spa
1. sa[i]=j,表示第i名的後綴從j開始code
**存的是下標**xml
2. rnk[i]=j,從i開始的後綴是第j名的htm
**與sa爲互逆運算,存的是值**blog
3. tp[i]=j, 第二關鍵字爲i的後綴從j開始排序
**可理解爲第二關鍵字的SA,存的是下標**字符串
插入解釋一下第一關鍵字和第二關鍵字:
咱們要對全部的後綴進行排序,怎麼排呢?
開始時,咱們每一個字符的後綴存的只有它本身,因此它後綴的大小就是它的ASCII碼。
咱們把每一個字符i當作(s[i],i)的二元組,若是咱們直接丟pair<int,int>裏面而後std::sort,
這樣的時間複雜度是O(log^2 n)的,顯然不夠優秀。
因此就須要用到基數排序RadixSort,不瞭解的自行百度。
再使用倍增法,就可使咱們排序的時間複雜度下降到O(logn)。
因此咱們要對每一個後綴的前兩個字母進行排序,第一個字母的相對關係已經獲得了。
第i個後綴的第二個字母,就是第i+1個後綴的第一個字母,利用這個關係咱們第二個字母的相對關係也就知道了。
咱們的tp數組就是用來記錄它的,rnk[i]表示上一輪中第i個後綴的排名。
這裏引用神仙attack的一句話,我以爲講的很是到位:
對於一個長度爲w的後綴,你能夠形象的理解爲:
第一關鍵字針對前w2個字符造成的字符串,第二關鍵字針對後w2個字符造成的字符串
而後對每一個後綴的前4個字母組成的字符串排序,前8個,前16個...這就是倍增法求SA的流程了。
給出RadixSort的代碼:
void RadixSort(int a[],int b[]){//基數排序 for(int i=0;i<=m;i++)tax[i]=0; for(int i=1;i<=n;i++)tax[a[i]]++; for(int i=1;i<=m;i++)tax[i]+=tax[i-1]; for(int i=n;i>=1;i--)sa[tax[a[b[i]]]--]=b[i]; }
實在不能理解RadixSort也沒有關係,代碼很短
再給出求SA的代碼:
bool cmp(int *r,int a,int b,int k){ return r[a]==r[b]&&r[a+k]==r[b+k]; } void getSA(int a[],int b[]){ for(int i=1;i<=n;i++) m=max(m,a[i]=s[i]-'0'),b[i]=i; RadixSort(a,b); for(int p=0,j=1;p<n;j<<=1,m=p){ p=0; for(int i=1;i<=j;i++)b[++p]=n-j+i; for(int i=1;i<=n;i++)if(sa[i]>j)b[++p]=sa[i]-j; RadixSort(a,b); int *t=a;a=b;b=t; a[sa[1]]=p=1; for(int i=2;i<=n;i++) a[sa[i]]=cmp(b,sa[i],sa[i-1],j)?p:++p; } }
關於代碼的解釋,有時間再填坑。本蒟蒻要學的算法還不少...SA就粗略地理解一下好了
開始填坑,先補充一個東西叫height數組。
height[i]表示排名爲i的後綴和排名爲i-1的後綴的最長公共前綴LCP。
暴力求解時間複雜度是O(n^2),根據一個性質height[i+1]>=height[i]-1
能夠O(n)時間內求出height數組,具體代碼:
void getHeight(){ for(int i=1,j=0;i<=n;i++){ if(j)j--; while(s[i+j]==s[sa[rnk[i]-1]+j])j++; height[rnk[i]]=j; } }
關於這個height數組,它能夠幹什麼,給出一張列表:
lcp(x,y)=min(heigh[x−y])lcp(x,y)=min(heigh[x−y]), 用rmq維護,O(1)查詢
height數組裏的最大值
首先二分答案x,對height數組進行分組,保證每一組的最小height都>=x
依次枚舉每一組,記錄下最大和最小長度,若sa[max]−sa[min]>=x那麼能夠更新答案
枚舉每個後綴,第i個後綴對答案的貢獻爲len−sa[i]+1−height[i]