乙個字串的題,有姿勢水平的oiers的腦中應該要浮現出許多演算法……
但是我沒有姿勢,也沒有水平,除了kmp和trie樹,什麼也想不起來。
直到我學了它——字尾陣列!
多虧這玩意兒,我現在什麼都想不起來了。
字尾陣列幹嘛用的?
主要處理同乙個字串中的重複子串問題。
如何實現?
注意到每乙個子串,都是乙個字尾的某個字首,這個字尾和字首都是唯一確定的。
而字尾相同的字首,和他們的字典序有密切聯絡。你有沒有想過,字典中的相鄰單詞,他們的公共字首總是很長。
乙個字串的任意字尾,都能用它的起始位置的下標唯一確定。
乙個字串的字尾陣列(suffixarray/sa),就是它的所有字尾字典序排序後的結果,當然不是儲存了所有的字尾,僅僅是儲存了它們的起始位置罷了。
這篇文章並不是要詳細說明字尾陣列能幹嘛的,怎麼實現。它只是讓你們對字尾陣列的實現有更直觀的理解,弄清楚**的每一行都是幹嘛的。
因為博主學習的時候,也弄不太清楚sa到底怎麼求出的,那個神奇的計數排序如何實現等等……
下面貼出乙個注釋詳盡的模板,來為大家理清思路。
1 #include2 #include3#define maxn 1000005
4int len, m=127;5
//len : 字串長度 , m : 不同排名的個數 / 字符集大小(初始時)
6char
str[maxn];7//
字串 , 從 1 開始編號
8int
rank[maxn], sa2[maxn];9//
rank : 第 i 次排序後的 rank / 第 i+1 次排序前的第一關鍵字
10//sa
2[k] : 第 i+1 次排序前的第二關鍵字第 k 小的字尾下標
11int
cnt[maxn], tmp[maxn];
12//
cnt : 桶排序用的"桶" , tmp : 重新計算 rank 的輔助陣列 , 這兩個陣列可以合併
13int
sa[maxn];
14//
字尾陣列 , sa[i] : 排名為 i 的字尾的起始位置
15int
height[maxn];
16//
height[i] = lcp(suffix(sa[i]), suffix(sa[i-1]))
17void
getheight()
26if(k) --k;
27int j=sa[rank[i]-1
];28
while(i+k<=len&&j+k<=len&&str[i+k]==str[j+k]) ++k;
29 height[rank[i]]=k;30}
31//
for(int i=1;i<=len;++i) printf("%d ",height[i]); puts("");32}
33 inline void
rsort()
43void
getsa()
60for(int i=1; i<=len; ++i) rank[i]=tmp[i];
61//
根據 sa 更新新的 rank
62 m=p;
63if(m==len) break;64
//不同排名的個數 m 更新成 p , 當所有排名都不同時 , 就可以退出了
65//
實踐證明這一句話加上會快(資料較隨機) , 但是理論上可以被卡掉
66//
printf("rk : "); for(int i=1;i<=len;++i) printf("%d ",rank[i]); puts("");
67//
printf("sa : "); for(int i=1;i<=len;++i) printf("%d ",sa[i]); puts("");
68//
printf("sa2: "); for(int i=1;i<=len;++i) printf("%d ",sa2[i]); puts("");
69//
輸出每步結果70}
71getheight();72}
73int
main()
接下來解釋一下**中難懂的部分,也是眾多blog沒有說清楚的地方:計數排序。
**37行:
for(int i=1; i<=len; ++i) ++cnt[rank[i]];
rank[i]表示第一關鍵字,cnt就是桶。
這只是在統計桶中元素罷了。
**38行:
for(int i=1; i<=m; ++i) cnt[i]+=cnt[i-1];
它把桶做了字首和,這樣的目的在於,我們知道了對於某乙個第一關鍵字,它所對應的區間:( cnt[i-1] , cnt[i] ],這是乙個左開右閉區間。
仔細思考一下是不是這樣。
**40行:
for(int i=len; i>=1; --i) sa[cnt[rank[sa2[i]]]--]=sa2[i];
對於確定的第一關鍵字,我們知道對應區間,但是在同乙個區間中,第二關鍵字還要保證公升序啊。
看看sa2陣列,它是第二關鍵字從小到大排序對應的字尾下標,那麼在 i 從大到小的迴圈過程中,sa2[i]就是滿足第二關鍵字從大到小的字尾下標。
那麼再在外面套乙個rank[sa2[i]]呢?如果有兩個 i 的rank[sa2[i]]是相同的,即它們的第一關鍵字相同,那麼我們說,是第二關鍵字大的先訪問到。
那麼在sa陣列中,這就表示成,在同乙個第一關鍵字下,按照著第二關鍵字從大到小的順序,陣列從後往前填充著字尾下標。
那就是說,在滿足了第一關鍵字有序的情況下,從前往後,第二關鍵字也是從小往大的。
這就是計數排序的原理,還是非常巧妙的。
新模板:
#include #include #include const int mn = 100005;int n, m;
char str[mn];
struct suffixarray
inline void buildsa()
for (int i = 1; i <= n; ++i) rk[i] = tmp[i];
sig = p;
if (sig == n) break;
} }inline void getheight()
} }int blcp[mn][21], bt;
inline void initst()
inline int lcp(int x, int y)
} c1, c2;
int main()
演算法學習 字尾陣列(SA)
參考部落格 定義 字尾 從第i位到字串結尾的子串 解決問題 從而解決 在字串中找子串 比較子串關係 查詢不同子串的數目 一般來說都是解決 字串和子串關係的問題 演算法學習 字尾陣列能夠在 nlogn的時間複雜度內求取出以下陣列 注意 明白字串包含字元的範圍 sa 儲存,第 i 個數字表示的是字典序第...
字尾陣列倍增演算法學習筆記
字尾陣列 suffix array 簡稱sa陣列裡面儲存的是字串s從小到大或從大到小的所有字尾。用處後續補,還不太清楚作用。首先牢牢記住sa i 意思是排第 i 的字尾是誰,而rank i 代表字尾 i 排第幾。然後先放上盜來的,其實這個演算法思路還是很簡單的,難的地方在於編碼。如果不了解基數排序的...
演算法學習 字尾陣列 height的求取
前置知識 字尾陣列 定義 lcp 全名最長公共字首,兩個字尾之間的最長字首,以下我們定義 lcp i j 的意義是字尾 i 和 j 的最長字首 z函式 函式z i 表示的是,第 i 個字尾和字串的最長字首 解決問題 這兩個演算法都是在解決這個問題 即求字尾和字串和字尾之間的最長公共字首 但是有所不同...