【數據結構】前綴樹/字典樹/Trie
【前綴樹】
用來保存一個映射(通常情況下 key 為字符串 value 為字符串所代表的信息)
例如:一個單詞集合 words = { apple, cat, water } 其中 key 為單詞 value 代表該單詞是否存在
words[ ‘apple‘ ] = 存在 而 word[ ‘ abc‘ ] = 不存在
圖示:一個保存了8個鍵的trie結構,"A", "to", "tea", "ted", "ten", "i", "in", and "inn".
鍵不需要被顯式地保存在節點中。圖示中標註出完整的單詞,只是為了演示trie的原理。
【應用】
trie樹常用於搜索提示。如當輸入一個網址,可以自動搜索出可能的選擇。當沒有完全匹配的搜索結果,可以返回前綴最相似的可能。
補充:
Trie是一種非常簡單高效的數據結構,但有大量的應用實例。
(1) 字符串檢索
事先將已知的一些字符串(字典)的有關信息保存到trie樹裏,查找另外一些未知字符串是否出現過或者出現頻率。
舉例:
@ 給出N 個單詞組成的熟詞表,以及一篇全用小寫英文書寫的文章,請你按最早出現的順序寫出所有不在熟詞表中的生詞。
@ 給出一個詞典,其中的單詞為不良單詞。單詞均為小寫字母。再給出一段文本,文本的每一行也由小寫字母構成。判斷文本中是否含有任何不良單詞。例如,若rob是不良單詞,那麽文本problem含有不良單詞。
(2)字符串最長公共前綴
Trie樹利用多個字符串的公共前綴來節省存儲空間,反之,當我們把大量字符串存儲到一棵trie樹上時,我們可以快速得到某些字符串的公共前綴。
舉例:
@ 給出N 個小寫英文字母串,以及Q 個詢問,即詢問某兩個串的最長公共前綴的長度是多少?
解決方案:首先對所有的串建立其對應的字母樹。此時發現,對於兩個串的最長公共前綴的長度即它們所在結點的公共祖先個數,於是,問題就轉化為了離線(Offline)的最近公共祖先(Least Common Ancestor,簡稱LCA)問題。
而最近公共祖先問題同樣是一個經典問題,可以用下面幾種方法:
1. 利用並查集(Disjoint Set),可以采用采用經典的Tarjan 算法;
2. 求出字母樹的歐拉序列(Euler Sequence )後,就可以轉為經典的最小值查詢(Range Minimum Query,簡稱RMQ)問題了;
(關於並查集,Tarjan算法,RMQ問題,網上有很多資料。)
(3)排序
Trie樹是一棵多叉樹,只要先序遍歷整棵樹,輸出相應的字符串便是按字典序排序的結果。
舉例:
@ 給你N 個互不相同的僅由一個單詞構成的英文名,讓你將它們按字典序從小到大排序輸出。
(4) 作為其他數據結構和算法的輔助結構
如後綴樹,AC自動機等
【模板】
該模板參考白書P209,用數組存儲字典樹
/* 字典樹模板(註意數組初始化問題) by chsobin 給定n個單詞的集合,詢問q次:一個單詞是否在集合中 此處附加信息為節點存不存在 */ #include<iostream> #include<cstdio> #include<cstring> using namespace std; const int maxnode = 100; //maxnode = 單詞長度*單詞數 int ch[maxnode][27]; int val[maxnode]; //附加信息 int sz = 1; //節點總數 //插入字符串s,附加信息為v。註意v必須非零,0代表本節點不是單詞節點 void insert(char * s, int v){ int u=0; int n = strlen(s); int c; for(int i=0;i<n;++i){ c = s[i] - ‘a‘; if(!ch[u][c]){ ch[u][c] = sz++; //新建節點 } u = ch[u][c]; } val[u] = v; //1表示該節點為單詞 } bool find(char * s){ int u=0; int n = strlen(s); int c; for(int i=0;i<n;++i){ c = s[i] - ‘a‘; if(ch[u][c]==0) return false; //單詞不存在 u = ch[u][c]; } if(val[u]) return true; //判斷是否為單詞節點 else return false; } int main(){ int n, q; char str[30]; scanf("%d%d", &n, &q); while(n--){ scanf("%s", str); insert(str, 1); //附加信息為1表示該節點為單詞節點 } while(q--){ scanf("%s", str); if(find(str)) printf("yes\n"); else printf("No\n"); } return 0; }
【例題】
hdu1251
/* 字典樹/前綴樹/Trie 2018年1月28日18:42:23 by chsobin 題目大意:給出n個單詞(單詞長度<=10), q個問題,問以給定字符串開頭的單詞有多少 樸素思想:遍歷 O(qn) 字典樹:建樹 O(10n) 查詢 O(10q) 建樹過程中維護節點額外信息val[i]表示建樹過程中經過節點i的單詞 */ #include<iostream> #include<cstdlib> #include<cstdio> #include<cstring> using namespace std; const int maxn = 1000000; struct Trie{ int ch[maxn][27]; int val[maxn]; int sz; Trie(){ sz = 1; memset(val, 0, sizeof(val)); memset(ch[0], 0, sizeof(ch[0])); } void insert(char * s){ int n = strlen(s); int u = 0; int c; for(int i=0;i<n;++i){ c = s[i] - ‘a‘; if(!ch[u][c]){ memset(ch[sz], 0, sizeof(ch[sz])); ch[u][c] = sz++; } u = ch[u][c]; val[u]++; } } int find(char * s){ int n = strlen(s); int u = 0; int c; for(int i=0;i<n;++i){ c = s[i] - ‘a‘; if(!ch[u][c]){ return 0; } u = ch[u][c]; } return val[u]; } }; Trie trie; char s[15]; int main(){ while(gets(s),strcmp(s,"")){ trie.insert(s); } while(gets(s)!=NULL){ printf("%d\n", trie.find(s)); } return 0; }ac 78ms
【參考】
http://dongxicheng.org/structure/trietree/
【數據結構】前綴樹/字典樹/Trie