字尾陣列模板及程式碼詳解
阿新 • • 發佈:2018-12-24
字尾陣列程式碼詳解
上圖中存在直邊和斜邊,下文會用到。
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int maxn = 1000;
const int maxasc = 128; //ascII碼[0,127]
char s[maxn];
int sa[maxn],t1[maxn],t2[maxn],c[maxn],n;
void debug()
{
for(int i=0; i<n; i++) printf("%d ",sa[i]);
printf("\n");
}
void build_sa(int m)
{
int *x = t1, *y = t2;//因為之後我們要swap(x,y)所以用指標
//第一遍實際就是對s陣列穩定排序,使用的是基數排序
for(int i=0; i<m; i++) c[i] = 0;//清空桶
for(int i=0; i<n; i++) c[x[i] = s[i]]++;//x[i]=s[i],x相當於是rank,存的是排名第i的串的首字母是什麼,當然現在還沒有排名
//把首字母相同的放在同一個桶裡
for(int i=1; i<m; i++) c[i] += c[i-1];//就根據首字母排序,首字母為i的前面留出首字母為1~i-1的數足夠位置
for(int i=n-1; i>=0; i--) sa[--c[x[i]]] = i;//sa[i]表示排名為i的串首字母在原串中的位置
//因為還沒有排完序,所以當前x[i]表示的還是i位置所引領的串的首字母(s[i]),所以i位置所引領的串的排名自然就是“--c[x[i]]”
//
for(int k=1; k<=n; k = k<<1) //每次擴充套件兩倍
{
int p = 0;
//利用sa陣列直接排序第二關鍵字
for(int i=n-k; i<n; i++) y[p++] = i;
//實際是認為沒有第二關鍵字,即不存在後k個字元的串的第二關鍵字最小,所以放在前面,y[i] = x 表示第二關鍵字第i小的串的首字母在i位置
for(int i=0; i<n; i++) if(sa[i] >= k) y[p++] = sa[i]-k;//第二關鍵字的位置本來是sa[i],-k之後就變成它對應第一關鍵字的位置(直邊所指)
//如果sa[i]小於k,第一關鍵字不夠k個字元,因為斜邊指向第二關鍵字,直邊就指向第一關鍵字,若是第二關鍵字小於看,有會指出去
//基數排序第一關鍵字
//已經知道第二關鍵字的大小順序了,扔到桶(c陣列)裡來一趟基數排序
for(int i=0; i<m; i++) c[i] = 0;
for(int i=0; i<n; i++) c[x[y[i]]]++;//y[i]表示第二關鍵字第i小的串對應第一關鍵字的首字母在i位置,x[i]表示i位置所引領的串的首字母
//所以x[y[i]]表示第二關鍵字第i小的串對應第一關鍵字的首字母,首字母相同的放在一個桶裡
for(int i=0; i<m; i++) c[i] += c[i-1];//就根據首字母排序,首字母為i的前面留出首字母為1~i-1的數足夠位置
for(int i=n-1; i>=0; i--) sa[--c[x[y[i]]]] = y[i];//y[i]表示第二關鍵字第i小的串對應第一關鍵字的首字母在i位置
//sa[i]表示排名為i的串首字母在原串中的位置,那麼在y[i]位置的串排名應該是“--c[x[y[i]]]”
//根據sa和y陣列計算x陣列
//x陣列實際是用來確定首字母為第i號字元,連續k個字母的串的相對大小的,所以最開始x[i]=s[i]
swap(x,y);//我們在下一次迴圈之前不會用到x所以不用在意x中到底存的是什麼,總是要覆蓋的。
//交換以後,我們原本捨棄的x,就變成捨棄y了
p = 1; x[sa[0]] = 0;
//從最小的串開始,賦予串一個整數值代表大小,aa = aa = 1,ab = 2,ba = 3等等
for(int i=1; i<n; i++)
x[sa[i]] = y[sa[i-1]] == y[sa[i]] && y[sa[i-1]+k] == y[sa[i]+k] ? p-1 : p++;//針對兩個串比較直邊所指元素和斜邊所指元素
//每個串都彼此大小不同了,事實上字尾就是應該所有都不相等的,相對大小已確定,退出迴圈
if(p >= n) break;
m = p; //關鍵字的取值範圍發生了變化,現在只有p個不同的值
}//最後就得到了sa和rank(x)
debug();
}
int main()
{
scanf("%s",s);
n = strlen(s);
build_sa(maxasc);
return 0;
}
void build_height(int *r, int n){//加一段求height的程式碼吧
int k=0, j;
for(int i=0; i<n; i++) rank[sa[i]] = i;//rank與sa是反函式
//如果在原串後面加了一個極小的字元(某些題目需要)
//就應該for(int i=1; i<=n; i++)//因為以這個極小字元開頭的字尾串一定是最小的,也就是s[0]
//我們0~n-1,n個字尾串也就變成了0~n,n個,所以for 1~n(網上大多數程式碼並沒有解釋這一點)
for(int i=1; i<=n; height[rank[i++]] = k)//height[rank[1]=0
for(k ? k--:0, j = sa[rank[i]-1]; r[i+k] == r[j+k]; k++);
//j就是排序後的上一個字尾。
//第i-2個字尾與第i-1個字尾的height為k,那麼第i-1個字尾與第i個字尾的height至少為k-1(可舉例驗證)於是從k-1向後擴充套件
}
詳細內容參看:字尾陣列——處理字串的有力工具