講義
字典樹,就像字典,在建完樹後,不斷查字典,建樹的過程也會查字典。
一般,我們假定0為字典樹的根節點,然後分為建樹和查詢兩個操作。
由於可能不斷插入新的元素,所以把建樹成為插入也不為過。
比如我們要插入一個“abc”到樹上。
那麼先看看根節點的孩子,“a”不存在,那就建一個!
那麼再看看“a”的孩子,"b"不存在,那麼再建。
c同理
這個時候我們給”c“這個節點打個標記,證明這是一個單詞的結尾了(根據題目而異,此處為了方便舉例)
我們再建一個“abe”
那麼先看看根節點的孩子,“a”存在,那就別建了,往下走即可。
那麼先看看“a“的孩子,“b”存在,那就別建了,往下走。
那麼先看看”b“的孩子,“e”不存在,那就建!
建完e,打標記。
那麼我們怎麼知道往下建的節點就是a,b,c,e?有26個英文字母嘛,a對應1,b對應2,c對應3,e對應5即可。
用陣列去存,那麼就是 tr[26]
由於不斷往下去建,我們還要一維存“層數”,這樣才知道每一層的字串是啥。就比如圖中的第4層有2個。也可以轉換為字串的最大長度。
也就是 tr[N][M],其中N是層數,M是26,代表每個字母(因題而異,M大小不固定,有可能有的題有數字,那麼M開到36,實際要預留多一點空間)
我們先看看插入如何實現
我們想要每一個節點對應一個idx,也就是定義一個節點的編號。
只有在該節點沒有編號的時候,才回去賦值一個編號,換句話說也就是該節點不存在,就給他一個編號
void insert(string s) { int p=0; for (int i=0; i<s.size(); i++) { int tem=s[i]-'a'+1; //把字元選轉換為數字 if (!tr[p][tem]) tr[p][tem]=++idx; //沒有這個節點,給他一個編號 p=tr[p][tem]; //沿著當前的節點往下走 } cnt[p]++; //給結尾字母打標記 }
再看看查詢,跟插入差不多。
還是那棵樹,我們查詢是否有“ab”這個單詞。
那麼先看看根節點的孩子,“a”存在,往下走。
那麼先看看“a"的孩子,“b”存在
此時發現遍歷完“ab“了,但是由於”b“節點這裡沒有標記,雖然是”abc“的字首,但是卻沒有出現過”ab”
我們查詢是否有“abda”這個單詞。
那麼先看看根節點的孩子,“a”存在,往下走。
那麼先看看“a"的孩子,“b”存在,往下走。
那麼先看看“b"的孩子,“d”不存在,返回0。
只要中途有某個字母沒出現過,那麼就可以證明單詞肯定沒出現過,後面的也不用查了。
我們查詢是否有“abc”這個單詞。
那麼先看看根節點的孩子,“a”存在,往下走。
那麼先看看“a"的孩子,“b”存在,往下走。
那麼先看看“b"的孩子,“c”存在。
此時發現遍歷完“abc“了,且”c“節點有標記,所以肯定出現過這個單詞
int query(string s) { int p=0; for (int i=0; i<s.size(); i++) { int tem=s[i]-'a'+1; //字元轉換成數字 if (!tr[p][tem]) return 0; //沒有這個單詞的某個字母,直接返回0 p=tr[p][tem]; //往下走 } return cnt[p]; //結尾初有標記才算出現過這個單詞 }
剛剛這裡講的就是一道模板題。接下來再溫故一遍程式碼。
板子
維護一個字串集合,支援兩種操作:
I x 向集合中插入一個字串 x;
Q x 詢問一個字串在集合中出現了多少次。
共有 N 個操作,所有輸入的字串總長度不超過 1e5,字串僅包含小寫英文字母。
輸入格式
第一行包含整數 N,表示運算元。
接下來 N 行,每行包含一個操作指令,指令為 I x 或 Q x 中的一種。
1≤N≤2e4
輸出格式
對於每個詢問指令 Q x,都要輸出一個整數作為結果,表示 x 在集合中出現的次數。
每個結果佔一行。
輸入/輸出例子1
輸入:
5
I abc
Q abc
Q ab
I ab
Q ab
輸出:
1
0
1
樣例解釋
無
#include <bits/stdc++.h> using namespace std; const int N=2e4+5, M=30; int n, tr[N][M], idx=0, cnt[N]; string op, s; void insert(string s) { int p=0; for (int i=0; i<s.size(); i++) { int tem=s[i]-'a'+1; if (!tr[p][tem]) tr[p][tem]=++idx; p=tr[p][tem]; } cnt[p]++; } int query(string s) { int p=0; for (int i=0; i<s.size(); i++) { int tem=s[i]-'a'+1; if (!tr[p][tem]) return 0; p=tr[p][tem]; } return cnt[p]; } int main() { scanf("%d", &n); while (n--) { cin>>op>>s; if (op=="I") insert(s); else printf("%d\n", query(s)); } return 0; }
優缺點
優點:
1.快速查詢字串S存不存在
2.快速查詢字串的字首
缺點:
空間佔用有時候較大