後綴數組
僅僅是我個人的理解,估計大家看不太懂,想從頭開始學的話還是看這兩篇博客吧,不過以後我會把博客更新完整的,先讓我消化消化
五分鐘搞懂後綴數組
最詳細的後綴數組
字符串的後綴數組其實就是對一個字符串的各個後綴字符字串進行排序後的結果。那麼難點卻是在排序上面,直接暴力對字符串進行排序肯定是不行的,時間複雜度是n方級的,通過觀察,你會發現後綴字符串之間是有聯繫的,你會得到這樣的結論,
當你求出各個字符串的第一個字符大小的排序之後,那麼你會發現,這些字符串的第二個字符大小的排序也已經求過了,
當你求出各個字符串的前兩個個字符大小的排序之後,那麼你會發現,這些字符串的第3,4個字符大小的排序也已經求過了,
所以就有人發明了倍增算法和基數排序相結合的方法進行字符串後綴數組的排序方法。
求後綴數組具體就是求sa和rank數組
sa[i]:排名是i的後綴數組的位置
rank[i]:位置是i的後綴數組的排名
輔助數組
tp[i]:功能類似與sa數組,用來記錄位置的,排名是i的字符串他的第二關鍵字的位置
tax[i]:用來計數的,用來記錄rank中相同排名出現了幾次
這個算法的精妙之處就是記錄主位相同序號的個數,然後通過次位序號的大小給更新sa
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int MAX=1e5+5;
int n,m;
int tax[MAX],rank[MAX],tp[MAX],sa[MAX];
char s[MAX];
void sort(){
for(int i=1;i<=m;i++)tax[i]=0;
for(int i=1;i<=n;i++)//i表示位置
tax[rank[i]]++;
for(int i=1;i<=m;i++)tax[i]+=tax[i-1];
//這裏體現的是基數排序,先是for循環遍歷次位,同時看一下第一位確定位置
for(int i=n;i>=1;i--)
sa[tax[rank[tp[i]]]--]=tp[i];
}
bool comp(int a,int b,int k){
return rank1[a]==rank1[b]&&rank1[a+k]==rank1[b+k];
}
//數組rank中可以重複數字,sa中是個不重複的數字,sa與rank 不斷的更新,找到最終的排序
//數組rank中可以有重複的數字,sa中是n個不重複的數字,通過sa與rank不斷的更新,找到最終的排序
//初始化rank,tp
//rank,tp更新sa,sa跟新rank ,tp
//當rank中有n個不同的數時,證明排完序了,而sa中從一開始就是n個不同的數,
//因爲比較的相同時,我們默認爲後綴數組開始下標在前的sa中的序號在前
/*
sa[i]=j ,表示第 i 名的後綴是從 j 開始的(注意存的是下標)
rank[i]=j ,從 i 開始的後綴是第 j 名的(和 sa 互逆,是排名(值))
tp[i] (b[i]) =j,第二關鍵字排序爲 i 的後綴是從 j 位置開始的 (相當於是第二關鍵字的 sa 數組,存的是下標)
tax[i]=j,表示第一關鍵字爲 排序爲i 的數,有 j 個(基數排序時用的桶,是值)
*/
void get_sa()
{
for(int i=1;i<=n;i++)
m=max(m,rank[i]=s[i]-'a'+1),tp[i]=i;//這個tp[i]=i,代表了第一次時,當第一位相同時,那個後綴在前那個序號在前
sort();
int p=1;
// 這裏是倍增算法的體現
for(j=1;;j<<=1)
{
if(p>=n) break;//結束條件
m=p;
int tot=0;
for(int i=n-j+1;i<=j;i++)tp[++tot]=i;
for(int i=1;i<=n;i++)//這裏的i代表的是位置加過k之後的排名
if(sa[i]>j)//(前2的k方個沒有,後2的k次方個沒有,這裏去除的是前2的k次方個)
tp[++tot]=sa[i]-j;
sort();
// int *t=rank;rank=tp;tp=t;
int rank1[MAX];
rank1=rank;
rank[sa[1]]=p=1;
//更新rank並離散化 ,如果主位和次位相等,p不自加
for(int i=2;i<=n;i++)//i代表的是排名
rank[sa[i]]=comp(rank1,sa[i],sa[i-1],j)?p:++p;
}
}
/*
bool comp(int r[],int a,int b,int k){
return r[a]==r[b]&&r[a+k]==r[b+k];
}
*/
int main(){
scanf("%s",s+1);//從下標是1的位置開始存儲。
n=strlen(s+1);
get_sa();
for(int i=1;i<=n;i++)printf("%d ",sa[i]);
}