字尾陣列 Suffix Array 學習筆記

2022-03-30 01:17:31 字數 3350 閱讀 8543

\(\\\)

介紹一些寫法和陣列的含義,首先要知道 字典序 。

加入我們提取出了 \(suffix(1)...suffix(len-1)\) ,將他們按照字典序從小到達排序。

顯然這兩個陣列可以在 \(o(n)\) 的時間內互相推出。

\(\\\)

由於博主太蒟並不會dc3,想看dc3的同志們可以溜了

\(\\\)

倍增構造法。

從小到大列舉 \(k\) ,每次按照字典序排序,每乙個字尾的長度為 \(2^k\) 的字首,直到沒有相同排名的為止。

若有的字尾不夠長就在後面補上:比當前串全字符集最小字元還要小的字元,結果顯然符合字典序的定義。

\(\\\)

如何確定長度為 \(2^k\) 的每乙個字尾對應字首的排名?

倍增。有點像數學歸納法的感覺。

首先我們顯然可以直接求出來 \(k=0\) 的答案。

然後對於乙個 \(k\) ,我們顯然已經完成了 \(k-1\) 部分的工作。

所以對於乙個長度為 \(2^k\) 的字首,它顯然可以由兩個長度為 \(2^\) 的字首拼成。

也就是說,我們可以把長度為 \(2^k\) 的字首,寫成兩個長度為 \(2^\) 的字首的有序二元組。

有乙個顯然的結論,因為長度 \(2^\) 的所有字首有序,所以我們對這些二元組排序法則可以寫成:

以前乙個長度為 \(2^\) 的字首的 \(rank\) 為第一關鍵字,以後乙個長度為 \(2^\) 的字首的 \(rank\) 為第二關鍵字排序。

對於此方法得到的順序,與將整個長度為 \(2^k\) 的字首字典序排序得到的順序,想一想發現是相同的,因為它符合字典序定義

\(\\\)

比較到什麼時候為止?顯然是求到乙個 \(k\),使得每乙個字尾 \(rank\) 不同時。

下面重點說一下**實現,演算法的精華也就體現在這裡。附上乙個寫的不錯的部落格 。

\(\\\)

再次宣告一些陣列的定義:

\(\\\)

第一步,將長度為 \(1\) 的每乙個字元排序。

這個過程就是基數排序。過程中的 \(n\) 表示陣列長度,\(m\) 表示原串字符集範圍為 \([1,m-1]\) 。

注意體會最後一行的倒序迴圈,此時體現了 \(rank\) 相同時按照第乙個字元在字串出現的位置排序的原則。

for(r int i=0;i\(\\\)

然後我們就要開始倍增構造,設 \(k\) 直接表示當前考慮的字首長度。

for(r int k=1,p=0;k<=n;k<<=1)

\(\\\)

首先看本次排序構造的 \(y[i]\) 。

由於 \(sa\) 陣列是有序的,所以我們沒必要對 \(y[i]\) 陣列進行一次基數排序。

p=0;

for(r int i=n-k;i=k) y[p++]=sa[i]-k;

第二行的含義是,因為字串的後 \(k\) 個字尾一定不能再找到長度為 \(k\) 的字尾繼續拼接了。

根據字典序的定義,空串字典序優於任何乙個字串,所以他們的 \(y\) 應該最靠前。

同時因為 \(rank\) 相同時按照第乙個字元在字串出現的位置排序的原則,迴圈是正序。

第三行的含義是,如果乙個長度為 \(k\) 的字首起始位置 \(\le k\) ,那它必然作為乙個後一段接在前面的某乙個位置上。

可以注意到的是, \(sa\) 陣列和 \(y\) 陣列的定義形式是一致的,也就是說, 我們按照 \(sa\) 的順序構造 \(y\) 沒有問題。

\(\\\)

然後就要構造 \(sa[i]\) 。這也是構造過程中最精華的一部分。

for(r int i=0;i這其實是乙個雙關鍵字基數排序的過程。

雙關鍵字基數排序時,我們需要先將第二關鍵字直接排序,然後再使用上面的**。

現在 \(y[i]\) 顯然已經是有序的了。

這個過程的理解可以參考最開始的單關鍵字基數排序。

為什麼那時我們做到了在 \(rank\) 相同時我們按照第乙個字元在字串出現的位置從小到大排序的要求?

因為我們是倒著掃瞄的。

同理,為了讓 \(x\) 相同的 \(y\) 越劣的越靠後,我們直接倒著掃瞄 \(y\) 不就可以了嗎!

此時我們成功在 \(sa\) 陣列內完成了第一第二關鍵字合併後的排序。

\(\\\)

然後要做的就是還原 \(rank\) 陣列了。

注意 \(rank\) 陣列的定義中可以有相同的排名,所以第一第二關鍵字 \(rank\) 相同的注意要特殊對待。

inline bool cmp(int *a,int x,int y,int k)

swap(x,y); p=1; x[sa[0]]=0;

for(r int i=1;i注意這個指標交換的過程,它優化掉了 \(swap\) 兩個陣列的複雜度。

因為 \(x\) 陣列是上乙個 \(k\) 的 \(rank\) 結果,所以可以直接比對新的即將拼合的兩段是否相同。

\(\\\)

最後還有乙個小優化。

if(p>=n) break;

m=p;

就是 \(p=n\) 時,可以發現當前長度的字首已經具有了區分每乙個字尾的作用,所以我們沒必要繼續比下去了。

\(\\\)

最後再多說一句,值得注意的是,不管是哪種實現方式,除了空字元外 \(rank\) 必須從 1 開始,否則會造成最小字元與空字元執行時混淆。

\(\\\)

給出乙個字串,寫出其所有迴圈同構串,將其按字典序從小大排序,輸出排序後每乙個串的尾字元。

\(\\\)

環的問題一般可以破環成鏈去搞。

拆開之後複製一倍接在後面,直接跑字尾陣列,按 \(sa\) 順序輸出所有長度大於 \(len\) 的字尾對應答案。

\(\\\)

#include#include#include#include#include#include#include#define n 200005

#define r register

using namespace std;

char ss[n];

int s[n],sa[n],cnt[n],t1[n],t2[n];

void da(int n,int m)

--n;

for(r int i=0;i

}int main()

Suffix Array 字尾陣列

顧名思義,suffixarray 以下有時簡稱sa 和字串的字尾有關。字尾 字串中某個位置一直到結尾的子串。sa中討論包括了原串和空串 所以共有len 1個字尾。字尾陣列 字串的所有字尾組成的按字典序從小到大排好的陣列。由於sa中記錄的都是字串的字尾,所以sa只需要記錄其表示的字尾的起始位置。由於比...

字尾陣列(Suffix Array)

字尾陣列是處理字串的有力工具。sa儲存乙個字串按字典序排列的字尾,如圖 rank陣列儲存字尾i的名次,就是把sa反過來,上圖中 rank 1 2,rank 2 8 height陣列儲存相鄰兩個sa字尾之間公共字首的長度,如圖 思路 用倍增的方法對每個字元開始的長度為2 k2 k 2k子字串進行排序,...

字尾陣列suffix array

倍增演算法,時間複雜度o nlogn sa從小到大儲存相對大小的下標 理解lsd,x陣列,sa陣列 char s maxn int sa maxn t maxn t2 maxn c maxn n void build sa int m void build sa int m int cmp suff...