8.1 Trie樹#
概念說明#
Trie(字典樹、前綴樹) 的每條「根到節點的路徑」代表一個字串前綴:根是空字串,往下每一步多吃一個字元。多個單字共用相同前綴的邊,例如 "car"、"cat"、"card" 共用 c-a-r 的路徑。每個節點維護「26(或任意字元集)個子女」與「是否為單字結尾」的標記。
Trie 的殺手級特性:
- 查詢成本 = O(單字長度),與詞庫有幾萬個字完全無關。
- 前綴查詢是天然的:走到前綴節點後,收集其子樹即可列出所有以該前綴開頭的單字。
- 無雜湊碰撞問題:不是「算一個雜湊值」,而是「逐字元對照」,行為完全可預測。
代價是空間:字元集大、單字又少時,稀疏節點很浪費;實務上常用字典代替定長陣列存子女,或用「雙陣列 Trie」壓縮。
操作步驟#
- insert(word):從根出發,逐字元「缺邊就建新節點」;走完後把末節點標為單字結尾。
- search(word):逐字元往下走;任一步斷頭即 False;走完後檢查「單字結尾」標記(走得到但沒標記,代表只是別的單字的前綴)。
- starts_with(prefix):走完前綴即可回 True,不必檢查結尾標記。
- autocomplete(prefix):走到前綴節點後做 DFS,收集所有標了結尾的路徑。
Python 實作#
class TrieNode:
def __init__(self):
self.children = {} # 字元 -> 節點
self.is_word = False
class Trie:
def __init__(self):
self.root = TrieNode()
def insert(self, word):
node = self.root
for ch in word:
if ch not in node.children:
node.children[ch] = TrieNode()
node = node.children[ch]
node.is_word = True
def _walk(self, s):
node = self.root
for ch in s:
if ch not in node.children:
return None
node = node.children[ch]
return node
def search(self, word):
node = self._walk(word)
return node is not None and node.is_word
def starts_with(self, prefix):
return self._walk(prefix) is not None
def autocomplete(self, prefix):
node = self._walk(prefix)
if node is None:
return []
results = []
def dfs(n, path):
if n.is_word:
results.append(prefix + path)
for ch, child in n.children.items():
dfs(child, path + ch)
dfs(node, "")
return results
t = Trie()
words = ["cat", "car", "card", "care", "dog", "do"]
for w in words:
t.insert(w)
print(t.search("card")) # True
print(t.search("ca")) # False(只是前綴,不是完整單字)
print(t.starts_with("ca")) # True
print(t.autocomplete("car")) # ['car', 'card', 'care']
print(t.autocomplete("d")) # ['dog', 'do']
與雜湊表的取捨#
查「單字存在嗎」,雜湊表 O(長度) 平均、夠快且省程式碼。但以下需求只有 Trie 能優雅勝任:
- 自動補全 / 前綴列舉:雜湊表必須掃全表 O(n),Trie 是 O(前綴長 + 結果總長)。
- 按字典序輸出:Trie 子女排序後 DFS 即得;雜湊表無序。
- 最長前綴匹配:如 IP 路由表,Trie 逐位元下走即得最長匹配。
複雜度分析#
insert / search / starts_with 都是 O(L)(L 為字串長度)。autocomplete 為 O(前綴長 + 輸出總字元數)。空間最壞 O(所有字元總數 × 字元集),但共用前綴會大幅節省;字元集大時用 dict 存子女,空間與實際出現的邊數成正比。
練習#
- 在 Trie 加上
count_words_starting_with(prefix):統計以指定前綴開頭的單字數。提示:讓每個節點記錄「子樹內單字數」,插入時沿途加一。 - 實作「最長公共前綴」:給一組單字建 Trie 後,找出它們共同的最長前綴(不必逐字兩兩比較)。
- 用 Trie 實作一個單字的「拼字建議」:查詢 misspelled 字時,列出「編輯距離 ≤ 1」的所有字典單字(提示:DFS 時允許一次替換、插入或刪除)。