Trie
前缀树(字典树)数据结构,高效的字符串检索。
函数签名
typescript
function createTrie(): Trie
interface Trie {
insert(word: string): void
search(word: string): boolean
startsWith(prefix: string): boolean
autocomplete(prefix: string): string[]
delete(word: string): boolean
getAllWords(): string[]
size(): number
clear(): void
}
interface TrieNode {
children: Map<string, TrieNode>
isEndOfWord: boolean
word?: string
}API 说明
insert(word: string): void
向 Trie 中插入一个单词。
参数:
word: 要插入的单词
时间复杂度:O(m),m 为单词长度
search(word: string): boolean
搜索单词是否存在。
参数:
word: 要搜索的单词
返回值:
- 单词存在返回
true,否则返回false
时间复杂度:O(m)
startsWith(prefix: string): boolean
检查是否存在以指定前缀开头的单词。
参数:
prefix: 要检查的前缀
返回值:
- 存在该前缀返回
true,否则返回false
时间复杂度:O(m)
autocomplete(prefix: string): string[]
获取所有以指定前缀开头的单词(自动补全)。
参数:
prefix: 前缀
返回值:
- 所有匹配的单词数组(已排序)
时间复杂度:O(m + n),m 为前缀长度,n 为匹配单词总长度
delete(word: string): boolean
删除一个单词。
参数:
word: 要删除的单词
返回值:
- 删除成功返回
true,单词不存在返回false
时间复杂度:O(m)
getAllWords(): string[]
获取所有单词。
返回值:
- 所有单词的数组(已排序)
时间复杂度:O(n),n 为所有单词的总长度
size(): number
获取单词数量。
返回值:
- Trie 中单词的数量
时间复杂度:O(1)
clear(): void
清空 Trie。
时间复杂度:O(1)
使用示例
typescript
import { createTrie } from '@shared/functions/data-structures/Trie'
// 创建 Trie
const trie = createTrie()
// 插入单词
trie.insert('apple')
trie.insert('app')
trie.insert('application')
trie.insert('apply')
trie.insert('banana')
// 搜索单词
console.log(trie.search('app')) // true
console.log(trie.search('appl')) // false
console.log(trie.search('banana')) // true
// 检查前缀
console.log(trie.startsWith('app')) // true
console.log(trie.startsWith('ban')) // true
console.log(trie.startsWith('cat')) // false
// 自动补全
console.log(trie.autocomplete('app'))
// ['app', 'apple', 'application', 'apply']
// 获取所有单词
console.log(trie.getAllWords())
// ['app', 'apple', 'application', 'apply', 'banana']
// 删除单词
trie.delete('app')
console.log(trie.search('app')) // false
console.log(trie.search('apple')) // true (不影响其他单词)
// 获取单词数量
console.log(trie.size()) // 4
// 清空
trie.clear()
console.log(trie.size()) // 0工作原理
Trie(前缀树)是一种树形数据结构,用于高效地存储和检索字符串:
节点结构:
- 每个节点包含一个字符和指向子节点的映射
isEndOfWord标记该节点是否为单词结尾word存储完整单词(用于自动补全)
插入操作:
- 从根节点开始,逐个字符向下遍历
- 如果字符不存在,创建新节点
- 最后标记单词结尾
搜索操作:
- 从根节点开始,逐个字符查找
- 检查最后节点是否标记为单词结尾
前缀匹配:
- 只需检查路径是否存在,无需检查是否为单词结尾
空间优化:通过共享公共前缀,Trie 可以节省大量空间。
应用场景
- 搜索引擎自动补全:输入前缀时显示建议
- 拼写检查:检查单词是否正确
- IP 路由表:最长前缀匹配
- 电话号码匹配:T9 输入法
- 字符串匹配:多模式匹配算法
- 词频统计:高效统计单词出现次数
经典算法
搜索引擎自动补全
typescript
function searchAutocomplete(trie: Trie, query: string, limit: number = 10): string[] {
// 每次用户输入时调用
const suggestions = trie.autocomplete(query)
return suggestions.slice(0, limit)
}
// 使用示例
const searchTrie = createTrie()
const keywords = [
'javascript', 'java', 'python', 'typescript',
'react', 'vue', 'angular', 'svelte'
]
keywords.forEach(word => searchTrie.insert(word))
console.log(searchAutocomplete(searchTrie, 'ja'))
// ['java', 'javascript']
console.log(searchAutocomplete(searchTrie, 'react'))
// ['react']拼写检查和建议
typescript
function spellCheck(trie: Trie, word: string): {
isCorrect: boolean
suggestions: string[]
} {
const isCorrect = trie.search(word)
if (isCorrect) {
return { isCorrect: true, suggestions: [] }
}
// 尝试去掉最后一个字符的前缀
const prefix = word.slice(0, -1)
const suggestions = trie.autocomplete(prefix)
return { isCorrect: false, suggestions }
}
// 使用示例
const dictTrie = createTrie()
['cat', 'car', 'card', 'care', 'careful'].forEach(w => dictTrie.insert(w))
console.log(spellCheck(dictTrie, 'care'))
// { isCorrect: true, suggestions: [] }
console.log(spellCheck(dictTrie, 'carx'))
// { isCorrect: false, suggestions: ['car', 'card', 'care', 'careful'] }单词搜索游戏(Boggle)
typescript
function findWords(board: string[][], dictionary: string[]): string[] {
const trie = createTrie()
dictionary.forEach(word => trie.insert(word))
const result = new Set<string>()
const rows = board.length
const cols = board[0].length
const visited = Array(rows).fill(0).map(() => Array(cols).fill(false))
function dfs(row: number, col: number, path: string) {
if (row < 0 || row >= rows || col < 0 || col >= cols || visited[row][col]) {
return
}
path += board[row][col]
if (!trie.startsWith(path)) {
return // 剪枝:没有以该前缀开头的单词
}
if (trie.search(path)) {
result.add(path)
}
visited[row][col] = true
// 探索 8 个方向
for (let dr = -1; dr <= 1; dr++) {
for (let dc = -1; dc <= 1; dc++) {
if (dr === 0 && dc === 0) continue
dfs(row + dr, col + dc, path)
}
}
visited[row][col] = false
}
for (let i = 0; i < rows; i++) {
for (let j = 0; j < cols; j++) {
dfs(i, j, '')
}
}
return Array.from(result)
}Trie vs 哈希表
| 特性 | Trie | 哈希表 |
|---|---|---|
| 精确查找 | O(m) | O(1) 平均 |
| 前缀查找 | O(m) | 不支持 |
| 自动补全 | O(m + n) | 不支持 |
| 空间效率 | 共享前缀 | 每个单词独立 |
| 有序遍历 | 支持 | 不支持 |
选择建议:
- 需要前缀匹配、自动补全 → 使用 Trie
- 只需要精确查找 → 使用哈希表
- 数据量小 → 使用哈希表(更简单)
性能特点
| 操作 | 时间复杂度 | 空间复杂度 |
|---|---|---|
| 插入 | O(m) | O(m) |
| 搜索 | O(m) | - |
| 前缀匹配 | O(m) | - |
| 自动补全 | O(m + n) | O(n) |
| 删除 | O(m) | - |
其中:
- m = 单词/前缀长度
- n = 匹配结果的总字符数
优化技巧
- 压缩 Trie:合并只有一个子节点的路径
- 使用数组代替 Map:对于小字符集(如 26 个字母)
- 延迟删除:标记删除而不是物理删除
- 添加缓存:缓存热门前缀的查询结果