Trie樹,也叫字典樹。顧名思義,它就是一個字典
字典是干什么的?查找單詞!(英文字典哦)
個人認為字典樹這個名字起得特別好,因為它真的跟字典特別像,一會r你就知道了。
注:trie的中文翻譯就是單詞查找樹
一、引入
先來看一個題:
給你n個單詞構成一個字典,再給你一個單詞,問此單詞在字典中有沒有出現。
簡單,暴力!
時間復雜度:n*單詞長度
再來看一個題:
給你n個單詞構成一個字典,再給你m個單詞,問這m個單詞在字典中有沒有出現。
再暴力!
時間復雜度:n*單詞長度+m*n*單詞長度
n≤1e4,m≤1e4,單詞長度≤1e3
boom!
(╯‵□′)╯︵┻━┻
所以,為什么要學習使用Trie樹?
因為它快!
二、概念
我們首先來看看Trie樹是啥
↑就是它
我們來解剖一下,好好研究研究(Trie樹:瑟瑟發抖)
首先,我們發現樹的每條邊上都有一個字母
這就是Trie樹的樣子,每條邊上都有一個字母,每個頂點代表從根到該節點的路徑所對應的字符串(根結點除外)
其次,有些節點是紅色的,有些則不是
這是什么意思呢?
結點不是代表單詞嘛
所以如果結點標紅,代表該單詞在字典中實際出現過
如果看不太懂,不要緊,繼續往下
三、插入單詞(構建Trie樹)
這里進入正題。第一步,構建trie樹
就好比你想要查找單詞,首先得有字典才行吧
step1:初始化
Trie樹為空,只包含一個孤零零的根結點
step2:插入單詞(注:這里假設所有單詞僅由小寫字母構成)
插入一個單詞的步驟如下:
(1)對於Trie樹,我們從根結點開始,設該節點為P;對於這個單詞,我們從第一個字母開始,設此字符為s
(2)掃描P下方的所有邊,看s有沒有出現過
如果出現了,設s與P→Q這條邊上的字符相同,則P=Q
如果沒有出現,另建一條邊,使該邊上的字母為s,新節點為Q,然后P=Q
(3)s變為該單詞的下一個字符,重復步驟2,直到掃描完整個單詞為止
以概念中的那個圖為例
首先我們要插入abc這個單詞
(1)P為根結點,s為'a'
發現根結點下方無'a',所以新建一條邊
(2)P為a下方那個結點,s為'b'
發現P下方無'b',所以新建一條邊
(3)P為b下方那個結點,s為'c'
發現P下方無'c',所以新建一條邊
(4)發現abc這個單詞插入完成,所以在當前的s做個標記,表示abc是一個出現過的單詞
接着我們按照相同的步驟插入bcd、efg和hij
接下來插入abcd
(1)P為根結點,s為'a'
發現根結點下方有'a',所以P變為'a'下方的結點
(2)P為a下方那個結點,s為'b'
發現P下方有'b',所以P變為'b'下方的結點
(3)P為b下方那個結點,s為'c'
發現P下方有'c',所以P變為'c'下方的結點
(4)P為c下方那個紅色的結點,s為'd'
發現P下方無'd',所以所以新建一條邊
(5)單詞abcd插入完畢,將當前的s做標記
下面按照相同的方法,繼續插入單詞abd和b
插入完成后Trie樹如下:
看看一樣不:
(右下那條邊應該是j)
我們可以發現,在Trie樹中有相同前綴的單詞共用相同的前綴,這樣就可以大大的優化空間和時間
插入單詞的時間復雜度為O(NE)(N為單詞個數,E為單詞長度)
參考代碼:

void insert(char *s)//s為要插入的字符串 { int len=strlen(s); int u=1;//1為根節點 for(int i=0;i<len;i++) { int c=s[i]-'a';//'a'有時需換成'A'或'0' if(!trie[u][c])//沒有共同前綴,建立一個新的 ch[u][c]=++tot;//tot為總點數 u=ch[u][c];//繼續向下插入單詞 } book[u]=true;//標記是一個出現過的單詞(圖中塗紅色) }
四、查找單詞
詞典有了,接下來就可以查詞了
在trie樹中查單詞就跟查字典一樣。先查首字母,然后第二個,第三個……
查找過程跟插入的過程很像:
(1)對於Trie樹,我們從根結點開始,設該節點為P;對於這個單詞,我們從第一個字母開始,設此字符為s
(2)掃描P下方的所有邊,看s有沒有出現過
如果出現了,設s與P→Q這條邊上的字符相同,則P=Q
如果沒有出現,則該單詞沒有出現過,直接返回false
(3)s變為該單詞的下一個字符,重復步驟2,直到掃描完整個單詞為止
(4)掃描完成后,判斷節點P有沒有被標記(是不是某個出現過的單詞的結尾)。如果標記了,那么該單詞出現過,返回true;如果沒有標記,那么該單詞是詞典中這個單詞的前綴,返回false。
對於(4)講解一下:
還是這棵Trie樹:
我們查找ef這個單詞:
(1)P為根結點,s為'e'
發現根結點下方有'e',所以P變為'e'下方的結點
(2)P為e下方那個結點,s為'f'
發現P下方有'f',所以P變為'f'下方的結點
(3)查找完成,發現P這里沒有標記,所以ef是詞典中單詞efg的前綴,而不是直接出現在了詞典里,返回false
再舉一個例子,我們查找abd這個單詞:
(1)P為根結點,s為'a'
發現根結點下方有'a',所以P變為'a'下方的結點
(2)P為a下方那個結點,s為'b'
發現P下方有'b',所以P變為'b'下方的結點
(3)P為b下方那個結點,s為'd'
發現P下方有'd',所以P變為'd'下方的結點
(3)查找完成,發現P這里有標記,所以abd是詞典中的單詞,返回true
應該講的挺明白的
查找的一個單詞的時間復雜度O(E),比起暴力的O(NE)要快多了
參考代碼:

bool find(char *s)//s為要查找的字符串 { int len=strlen(s); int u=1;//1為根節點 for(int i=0;i<len;i++) { int c=s[i]-'a';//'a'有時需換成'A'或'0' if(!trie[u][c])//單詞沒有出現,直接返回false return false; u=ch[u][c];//繼續向下查找單詞 } //如果掃描完了這個單詞 return true;//是某個單詞的前綴 }

bool find(char *s)//s為要查找的字符串 { int len=strlen(s); int u=1;//1為根節點 for(int i=0;i<len;i++) { int c=s[i]-'a';//'a'有時需換成'A'或'0' if(!trie[u][c])//單詞沒有出現,直接返回false return false; u=ch[u][c];//繼續向下查找單詞 } //如果掃描完了這個單詞 return book[u];//如果出現過,返回true;如果沒有出現過(是前綴),返回false }
模板題:
https://www.cnblogs.com/llllllpppppp/p/9366344.html
本文部分圖片來源於網絡
部分內容參考《信息學奧賽一本通.提高篇》第二部分第三章 Trie字典樹
若需轉載,請注明https://www.cnblogs.com/llllllpppppp/p/9449846.html
~祝大家編程順利~