字尾陣列詳解+模板
注
SA[] 第幾名是誰
字尾陣列:字尾陣列 SA 是一個一維陣列, 它儲存 1..n 的某個排列 SA[1] ,SA[2],……,SA[n],並且保證 Suffix(SA[i]) < Suffix(SA[i+1]),1≤i<n 。也就是將 S 的 n 個字尾從小到大進行排序之後把排好序的字尾的開頭位置順次放入 SA 中。
Rank[] 誰是第幾名名次陣列:名次陣列 Rank[i]儲存的是 Suffix(i)在所有後綴中從小到大排列的“名次 ” 。
r[]:原始資料j當前字串的長度,每次迴圈根據2個j長度的字串的排名求得2j長度字串的排名.
y[]:指示長度為2j的字串的第二關鍵字的排序結果,通過儲存2j長字串的第一關鍵字的下標進行指示.
wv[]:2j長字串的第一關鍵字的排名序號.
ws[]:計數陣列,計數排序用到.
x[]:一開始是原始資料r的拷貝(其實也表示長度為1的字串的排名),之後表示2j長度字串的排名.
p:不同排名的個數.
片段
1.對長度為1的字串進行排序(函式的第一步)
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;
①用的是基數排序,也可以使用其它的排序
②r[]儲存原本輸入的字串,x[]是對r[]的ASCII呈現(便於排序)
③m是一個估計數字,代表ASCII最大值,在迴圈中做邊界
④n在這裡是字串的長度+1,後面的加加減減有所體現(貌似不介意直接用字串的長度)
⑤最後一行比較難懂,但實踐證明它確實是正確的,sa[i]=j表示第i名是j。
ws[i]是對第i及之前字元出現次數的累加,越往後ws[i]越大,而且對應的字元數值越大,舉個例子,如果某一字串為aaabaa,則a出現的次數為5,b出現的次數為1,按上述原理,可以看做ws[a]=5,ws[b]=6,固然a都在前5名,b在第六名。
對aabaaaab進行輸出後為801345627,按照sa的定義對應起來
aabaaaab ~
23845679 1 非常正確
理解了這個,最後一行就能明白了
2.進行若干次基數排序
因為前面排序的名次可能有重複,所以要再進行若干次,直到所有的名次都不再相同
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++;
}
相對於上面函式的第一步來說,這一坨程式碼更加複雜了
①從最外層迴圈可以看出,j是處於倍增狀態的,代表正在比較的每一小段字串的長度
②迴圈內的第一行,迴圈了j-1次,是對後面幾個數的提前處理(其第二關鍵字都為0)如圖
即所有加0的數
③第二行,再翻上去看一眼sa的作用。首先要明白這一行拋棄了一些東西,
由於是對第二關鍵字的排序,第一關鍵字先不看,所以有一條件if(sa[i]>=j)
這條語句後面y[p++]=sa[i]-j,要減去j也是因為這個
到這裡,第二關鍵字的排序就完成了
④開始第一關鍵字的排序
假設需要排序的數為92 71 10 80 63 90
那麼y[]=3 4 6 2 1 5 即對第二關鍵字排序後名次遞增所對應的序號
x[]=10 80 90 71 92 63 即對第二關鍵字排序的結果
for(i=0; i<n; i++) wv[i]=x[y[i]];將x[]陣列拷貝到wv[]中
完整的程式碼(參考理解)
#include <cstdio>
#include <iostream>
#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]]++;//以字元的ascii碼為下標
for(i=1; i<m; i++) Ws[i]+=Ws[i-1];
for(i=n-1; i>=0; i--) sa[--Ws[x[i]]]=i;
/*cout<<"SA"<<endl;;
for(int i=0;i<n+1;i++)cout<<sa[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;
}