9. 字典树(Trie / 前缀树)
9. 字典树(Trie / 前缀树)
问题:需要按前缀快速查找字符串,如搜索自动补全、拼写检查、IP 路由。
核心:每个节点代表一个字符,从根到某节点的路径对应一个字符串前缀。查找只与字符串长度 m 有关,与总数据量无关。
特点
- 每条边代表一个字符,根到节点路径 = 字符串
- 查找/插入时间 O(m),m 为字符串长度
- 节点可标记是否为单词结尾
优点
- 前缀查找极快,O(m) 与数据量无关
- 天然支持前缀匹配、自动补全
- 无哈希冲突问题
缺点
- 空间开销大,每个字符一个节点
- 只适合字符串场景
- 大量公共前缀少时效率不如哈希表
class TrieNode {
children = {};
isEnd = false;
}
class Trie {
#root = new TrieNode();
insert(word) {
let node = this.#root;
for (const ch of word) {
node.children[ch] ??= new TrieNode();
node = node.children[ch];
}
node.isEnd = true;
}
search(word) {
const node = this.#walk(word);
return node?.isEnd ?? false;
}
startsWith(prefix) {
return this.#walk(prefix) !== null;
}
#walk(str) {
let node = this.#root;
for (const ch of str) {
if (!node.children[ch]) return null;
node = node.children[ch];
}
return node;
}
// 自动补全:返回所有以 prefix 开头的词
autocomplete(prefix) {
const node = this.#walk(prefix);
if (!node) return [];
const results = [];
const dfs = (n, path) => {
if (n.isEnd) results.push(prefix + path);
for (const [ch, child] of Object.entries(n.children)) {
dfs(child, path + ch);
}
};
dfs(node, '');
return results;
}
}
const trie = new Trie();
['apple', 'app', 'application', 'banana'].forEach(w => trie.insert(w));
console.log(trie.search('app')); // true
console.log(trie.search('ap')); // false
console.log(trie.startsWith('ap')); // true
console.log(trie.autocomplete('app')); // ['app', 'apple', 'application']package trie
type TrieNode struct {
Children map[rune]*TrieNode
IsEnd bool
}
func newTrieNode() *TrieNode {
return &TrieNode{Children: make(map[rune]*TrieNode)}
}
type Trie struct {
root *TrieNode
}
func NewTrie() *Trie {
return &Trie{root: newTrieNode()}
}
func (t *Trie) Insert(word string) {
node := t.root
for _, ch := range word {
if _, ok := node.Children[ch]; !ok {
node.Children[ch] = newTrieNode()
}
node = node.Children[ch]
}
node.IsEnd = true
}
func (t *Trie) Search(word string) bool {
node := t.walk(word)
return node != nil && node.IsEnd
}
func (t *Trie) StartsWith(prefix string) bool {
return t.walk(prefix) != nil
}
func (t *Trie) walk(s string) *TrieNode {
node := t.root
for _, ch := range s {
child, ok := node.Children[ch]
if !ok {
return nil
}
node = child
}
return node
}搜索引擎自动补全、拼写检查、IP 路由表(路由前缀匹配)、IDE 代码补全都使用字典树加速前缀查找。