概念
Trie
(也叫前缀树或字典树)是一种树形数据结构,主要用于快速检索字符串集合中的前缀匹配,常见于词典、自动补全、IP 路由等场景。
特点
- 共享前缀:Trie 树可以让多个字符串共用相同的前缀路径,从而节省空间;
- 高效查询:Trie 的查找和插入操作的时间复杂度为 O(m),其中
m
是字符串的长度; - 动态增长:可以随时插入新单词,并适应大规模数据;
- 支持前缀匹配:能够快速检查是否存在以某个字符串为前缀的单词,这在自动补全系统中非常有用;
示意图
用 Trie 树存储以下单词:
apple
app
application
banana
bat
Root
├── a
│ └── p
│ └── p (end)
│ ├── l ─── e (end)
│ └── l ─── i ─── c ─── a ─── t ─── i ─── o ─── n (end)
└── b
├── a ─── n ─── a ─── n ─── a (end)
└── a ─── t (end)
常用操作
插入操作
- 从根节点开始遍历字符串的每个字符;
- 如果当前字符不存在于子节点中,则创建一个新节点;
- 当所有字符插入完成时,将最后一个节点标记为单词结束;
查找操作
- 从根节点开始逐字符遍历;
- 如果所有字符都匹配,且最后节点标记为单词结束,则说明该单词存在;
前缀匹配(StartsWith)操作
- 从根节点逐字符匹配,判断给定前缀是否存在于树中;
优缺点
优点
- 快速检索:查找、插入和前缀匹配的效率高,复杂度为 O(m)。
- 支持前缀匹配查询:适合实现搜索引擎的自动补全功能。
- 无需额外的哈希函数:与哈希表相比,Trie 不依赖于哈希函数,避免哈希冲突。
缺点
- 空间开销较大:每个节点都需要存储子节点,若字符串过长或字符种类多,可能导致大量空间占用。
- 存储的字符串种类有限:若字符集过于庞大(如UTF-8编码),Trie 的空间消耗会变得更高。
时间复杂度分析
- 插入(Insert):
O(m)
,其中m
是字符串的长度。 - 查找(Search):
O(m)
。 - 前缀匹配(StartsWith):
O(m)
。
代码实现(c++)
#include <iostream>
#include <unordered_map>
#include <memory>
using namespace std;
class TrieNode {
public:
std::unordered_map<char, std::unique_ptr<TrieNode>> children; // 子节点
bool isEndOfWord = false; // 标记是否为单词的结束
TrieNode() = default; // 默认构造函数
};
class Trie {
private:
std::unique_ptr<TrieNode> root; // 根节点
public:
Trie() {
root = std::make_unique<TrieNode>();
}
// 插入一个字符串
void insert(const string& word) {
TrieNode* node = root.get();
for (char c : word) {
if (!node->children[c]) {
node->children[c] = std::make_unique<TrieNode>();
}
node = node->children[c].get();
}
node->isEndOfWord = true;
}
// 查找一个字符串是否存在于 Trie 中
bool search(const string& word) const {
TrieNode* node = root.get();
for (char c : word) {
if (!node->children.count(c)) {
return false;
}
node = node->children[c].get();
}
return node->isEndOfWord;
}
// 判断是否存在以给定前缀开头的字符串
bool startsWith(const string& prefix) const {
TrieNode* node = root.get();
for (char c : prefix) {
if (!node->children.count(c)) {
return false;
}
node = node->children[c].get();
}
return true;
}
};
int main() {
Trie trie;
// 插入单词
trie.insert("apple");
trie.insert("app");
trie.insert("application");
trie.insert("banana");
trie.insert("bat");
// 查找单词
cout << boolalpha << trie.search("apple") << endl; // true
cout << trie.search("app") << endl; // true
cout << trie.search("appl") << endl; // false
// 前缀匹配
cout << trie.startsWith("app") << endl; // true
cout << trie.startsWith("ban") << endl; // true
cout << trie.startsWith("cat") << endl; // false
return 0;
}