演算法學習 字尾陣列

2022-03-29 06:37:18 字數 3380 閱讀 3132

乙個字串的題,有姿勢水平的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 個字尾和字串的最長字首 解決問題 這兩個演算法都是在解決這個問題 即求字尾和字串和字尾之間的最長公共字首 但是有所不同...