Trie樹
Trie這個名字取自“retrieval”,檢索,因為Trie可以只用一個字首便可以在一部字典中找到想要的單詞。
雖然發音與「Tree」一致,但為了將這種 字典樹 與 普通二叉樹 以示區別,程式設計師小吳一般讀「Trie」尾部會重讀一聲,可以理解為讀「TreeE」。
Trie 樹,也叫“字典樹”。顧名思義,它是一個樹形結構。它是一種專門處理字串匹配的資料結構,用來解決在一組字串集合中快速查詢某個字串的問題。
此外 Trie 樹也稱字首樹(因為某節點的後代存在共同的字首,比如pan是panda的字首)。
它的key都為字串,能做到高效查詢和插入,時間複雜度為O(k),k為字串長度,缺點是如果大量字串沒有共同字首時很耗記憶體。
它的核心思想就是通過最大限度地減少無謂的字串比較,使得查詢高效率,即「用空間換時間」,再利用共同字首來提高查詢效率。
Trie樹的特點
假設有 5 個字串,它們分別是:code,cook,five,file,fat。現在需要在裡面多次查詢某個字串是否存在。如果每次查詢,都是拿要查詢的字串跟這 5 個字串依次進行字串匹配,那效率就比較低,有沒有更高效的方法呢?
如果將這 5 個字串組織成下圖的結構,從肉眼上掃描過去感官上是不是比查詢起來會更加迅速。
通過上圖,可以發現 Trie樹 的三個特點:
- 根節點不包含字元,除根節點外每一個節點都只包含一個字元
- 從根節點到某一節點,路徑上經過的字元連線起來,為該節點對應的字串
- 每個節點的所有子節點包含的字元都不相同
通過動畫理解 Trie 樹構造的過程。在構造過程中的每一步,都相當於往 Trie 樹中插入一個字串。當所有字串都插入完成之後,Trie 樹就構造好了。
Trie樹的插入操作
Trie樹的插入操作很簡單,其實就是將單詞的每個字母逐一插入 Trie樹。插入前先看字母對應的節點是否存在,存在則共享該節點,不存在則建立對應的節點。比如要插入新單詞cook
,就有下面幾步:
- 插入第一個字母
c
,發現root
節點下方存在子節點c
,則共享節點c
- 插入第二個字母
o
,發現c
節點下方存在子節點o
,則共享節點o
- 插入第三個字母
o
,發現o
節點下方不存在子節點o
,則建立子節點o
- 插入第三個字母
k
,發現o
節點下方不存在子節點k
,則建立子節點k
- 至此,單詞
cook
中所有字母已被插入 Trie樹 中,然後設定節點k
中的標誌位,標記路徑root->c->o->o->k
這條路徑上所有節點的字元可以組成一個單詞cook
Trie樹的查詢操作
在 Trie 樹中查詢一個字串的時候,比如查詢字串 code
,可以將要查詢的字串分割成單個的字元 c,o,d,e,然後從 Trie 樹的根節點開始匹配。如圖所示,綠色的路徑就是在 Trie 樹中匹配的路徑。
如果要查詢的是字串cod
(鱈魚)呢?還是可以用上面同樣的方法,從根節點開始,沿著某條路徑來匹配,如圖所示,綠色的路徑,是字串cod
匹配的路徑。但是,路徑的最後一個節點「d」並不是橙色的,並不是單詞標誌位,所以cod
字串不存在。也就是說,cod
是某個字串的字首子串,但並不能完全匹配任何字串。
程式設計師不要當一條鹹魚,要向
cook
靠攏:)
Trie樹的刪除操作
Trie樹的刪除操作與二叉樹的刪除操作有類似的地方,需要考慮刪除的節點所處的位置,這裡分三種情況進行分析:
刪除整個單詞(比如hi
)
- 從根節點開始查詢第一個字元
h
- 找到
h
子節點後,繼續查詢h
的下一個子節點i
i
是單詞hi
的標誌位,將該標誌位去掉i
節點是hi
的葉子節點,將其刪除- 刪除後發現
h
節點為葉子節點,並且不是單詞標誌位,也將其刪除 - 這樣就完成了
hi
單詞的刪除操作
刪除字首單詞(比如cod
)
這種方式刪除比較簡單。
只需要將cod
單詞整個字串查詢完後,d
節點因為不是葉子節點,只需將其單詞標誌去掉即可。
刪除分支單詞(比如cook
)
與 刪除整個單詞 情況類似,區別點在於刪除到 cook
的第一個 o
時,該節點為非葉子節點,停止刪除,這樣就完成cook
字串的刪除操作。
Trie樹的應用
事實上 Trie樹 在日常生活中的使用隨處可見,比如這個:
具體來說就是經常用於統計和排序大量的字串(但不僅限於字串),所以經常被搜尋引擎系統用於文字詞頻統計。它的優點是:最大限度地減少無謂的字串比較,查詢效率比雜湊表高。
1. 字首匹配
例如:找出一個字串集合中所有以 五分鐘
開頭的字串。我們只需要用所有字串構造一個 trie樹,然後輸出以 五−>分−>鍾 開頭的路徑上的關鍵字即可。
trie樹字首匹配常用於搜尋提示。如當輸入一個網址,可以自動搜尋出可能的選擇。當沒有完全匹配的搜尋結果,可以返回字首最相似的可能
2. 字串檢索
給出 N 個單片語成的熟詞表,以及一篇全用小寫英文書寫的文章,按最早出現的順序寫出所有不在熟詞表中的生詞。
檢索/查詢功能是Trie樹最原始的功能。給定一組字串,查詢某個字串是否出現過,思路就是從根節點開始一個一個字元進行比較:
- 如果沿路比較,發現不同的字元,則表示該字串在集合中不存在。
- 如果所有的字元全部比較完並且全部相同,還需判斷最後一個節點的標誌位(標記該節點是否代表一個關鍵字)。
Trie樹的侷限性
如前文所講,Trie的核心思想是空間換時間,利用字串的公共字首來降低查詢時間的開銷以達到提高效率的目的。
假設字元的種數有m
個,有若干個長度為n的字串構成了一個 Trie樹 ,則每個節點的出度為 m
(即每個節點的可能子節點數量為m
),Trie樹 的高度為n
。很明顯我們浪費了大量的空間來儲存字元,此時Trie樹的最壞空間複雜度為O(m^n)
。也正由於每個節點的出度為m
,所以我們能夠沿著樹的一個個分支高效的向下逐個字元的查詢,而不是遍歷所有的字串來查詢,此時Trie樹的最壞時間複雜度為O(n)
。
這正是空間換時間的體現,也是利用公共字首降低查詢時間開銷的體現。