1. 程式人生 > 實用技巧 >07 字典樹 KMP 字尾陣列(希望我今天能搞完)

07 字典樹 KMP 字尾陣列(希望我今天能搞完)

字典樹

定義

其本質就是用一顆樹來記錄大量的字串,主要適用於長度較小但是個數較多的情況,是一種資料結構。其核心思想是在插入時充分利用已有的字串,也就是合併不同字串的公共字首。字典樹的主要功能就是查詢,每次的查詢只和字串長度有關所以就是$O(1)$複雜度,又由於其結構可以用來求兩個字串的$lcp$。

實現

這裡用的是基地裡的王大佬的板子,程式碼中的$ch$陣列就是樹,第一維是樹的層(我自己瞎叫的,區別於深度),第二維對應$26$個字母(當然可以擴充,只要改變一下巨集定義裡的$id$就可以了),$end$陣列是記錄第$i$個節點是否為一個字串的末尾,先上程式碼:

 1 struct Trie{
2 int tot,ch[maxn][30],end[maxn]; 3 void init(){//#define cl(x) memset(x,0,sizeof(x)) 4 cl(ch),cl(end),tot=1; 5 } 6 int append(int pos,int c){//在pos節點上插入編號為c('a'為0,依次往後)的字元 7 int &t=ch[pos][c]; 8 return t?t:t=++tot; 9 } 10 int insert(string s){//#define id(x) (x-'a')
11 int pos=1,i; 12 for(int i=0;i<s.size();i++) 13 pos=append(pos,id(s[i])); 14 end[pos]++; 15 return pos; 16 } 17 bool find(string a){ 18 ll u,v; 19 u=0; 20 for(int i=0;i<a.size();i++){ 21 v=id(a[i]); 22 if
(!ch[u][v]) return 0; 23 u=ch[u][v]; 24 } 25 if(!end[u]) return 0; 26 return 1; 27 } 28 }tree;

前兩個函式直接看的話不太清楚,這裡給出一組輸入輸出結果就更清楚了:

 1 input:
 2 rfcytcu
 3 rfcfe
 4 
 5 output:
 6 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
 7 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 0 0 0 0 0 0 0 0
 8 0 0 0 0 0 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
 9 0 0 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
10 0 0 0 0 0 9 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 5 0
11 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 6 0 0 0 0 0 0
12 0 0 7 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
13 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 8 0 0 0 0 0
14 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
15 0 0 0 0 10 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
16 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

輸出是$ch$陣列,具體程式碼就不貼了。可以看到實際的$ch[0]$就是根節點所在的那一層。在查詢的時候一開始將$pos$賦值為$1$,也就是從$ch[1]$開始查詢,查詢的位置就是字串當前字元的編號,然後如果不為$0$就說明已經插入了該字元,那麼就繼續往下查詢,查詢的層數編號恰好就是這個非零的值。在查詢的時候有三種情況:

1. 當走到被查詢字元的末尾時恰好停止,此時判斷$end[i]$如果不為$0$就說明這裡是一個已經插入的字元說明字元存在;

2. 如果字串已經結束但是還未到任何一個$end$非零的節點說明字元不存在;

3. 如果樹已經到了一個盡頭(就是後面沒有子節點了),看到第$14$行,此時這一整行為$0$,那麼就說明字串不存在,這得益於在插入時的處理。

思考

1. 求出整個集合中所有的公共字首。

遍歷整棵樹(一開始腦抽了,以為遍歷是指數的,因為要遍歷的話要一層一層的迴圈,但其實由於總的個數是小於等於$n$的,所以其實複雜度是$O(26n)$的),畫圖知道其實就是總節點數減去葉子節點。用$dfs$每次如果有一行不為零就跳到那一行,如果這個下一行全為$0$就說明這是一個葉子;總節點數就是結構體裡面的$tot$,然後就完了。

2. 給出一個字串,問有多少字串含有該字首。

多開一個數組記錄經過該節點的個數,然後改一下$find$函式,如果存在就返回經過該點的次數。