目录
前缀树(Trie,有时也称为字典树或关键词树)是一种有序树结构,用于存储具有相同前缀的字符串集合。它非常适合处理字符串搜索和匹配问题,并且可以显著提高查询速度
1、应用场景
- 文本搜索和自动补全:搜索引擎、输入法、智能提示等。
- 拼写检查:拼写校正工具。
- IP路由:网络路由表查找。
- 文件系统:文件路径的快速查找。
- 生物信息学:DNA序列的匹配和分析。
- 前端路由系统:Web应用中的URL匹配。
- 词频统计:文本处理和分析。
- 语法分析:编译器和解析器中的关键字识别
2、原理
前缀树的基本结构是一棵树,每个节点代表一个字符
- 树的根节点不包含字符,除根节点外每一个节点都还包含一个字符
- 从根节点到某一节点,将路径上经过的字符连接起来,就构成了一个字符串
- 每个节点的所有的子节点包含的字符都不相同
3、特点
节点结构:每个节点通常代表一个字符,并且可能包含指向其他字符节点的指针。
边的含义:边通常标记了字符,连接不同节点形成路径。
存储方式:节点可以包含一个标志位来表示该节点是否代表了一个完整的字符串的结尾。
公共前缀共享:如果多个字符串有相同的前缀,则它们会在前缀树中共享相同的路径。
4、优缺点
优点
- 高效的搜索:查找一个字符串的时间复杂度取决于字符串的长度,而不是字符串集合的大小
- 节省空间:通过共享公共前缀,可以减少存储空间的需求
- 便于实现自动完成和建议:可以轻松地找到以特定前缀开始的所有字符串
缺点
- 空间消耗:在某些情况下,前缀树可能会占用相对较多的空间,尤其是在字符集较大或者字符串集合中没有太多公共前缀的情况下
- 维护成本:插入和删除操作可能比简单的数组或哈希表更复杂
5、实现前缀树
首先需要一个TrieNode 类
-children字典:用于存储子节点
-isEndOfWord:来表示该节点是否一个完整单词的结尾
前缀树一般用于存储字符串,字符集相对较小(26个字母),但在其他语言(如汉字、符号等)中,字符集可能非常大且不固定,因此此处使用Map可以动态地添加子节点,而不需要预定义一个固定大小的数组
class TrieNode {
Map<Character, TrieNode> children; // 子节点
boolean isEndOfWord; // 是否是完整单词的标志
public TrieNode() {
children = new HashMap<>();
isEndOfWord = false;
}
}
Trie 类:包含插入、查找和前缀检查的方法。
- insert(String word) :插入一个单词到前缀树中。
- search(String word) :查找一个单词是否存在于前缀树中。
- startsWith(String prefix) :检查是否有单词以给定前缀开头
class Trie {
private TrieNode root;
public Trie() {
root = new TrieNode();
}
// 插入单词
public void insert(String word) {
TrieNode node = root;
for (char ch : word.toCharArray()) {
node.children.putIfAbsent(ch, new TrieNode());
node = node.children.get(ch);
}
node.isEndOfWord = true;
}
// 查找单词
public boolean search(String word) {
TrieNode node = root;
for (char ch : word.toCharArray()) {
if (!node.children.containsKey(ch)) {
return false; // 字符不在子节点中
}
node = node.children.get(ch);
}
return node.isEndOfWord; // 返回是否是完整单词
}
// 检查前缀
public boolean startsWith(String prefix) {
TrieNode node = root;
for (char ch : prefix.toCharArray()) {
if (!node.children.containsKey(ch)) {
return false; // 前缀不存在
}
node = node.children.get(ch);
}
return true; // 前缀存在
}
}
测试:
public static void main(String[] args) {
Trie trie = new Trie();
trie.insert("apple");
System.out.println(trie.search("apple")); // 输出: true
System.out.println(trie.search("app")); // 输出: false
System.out.println(trie.startsWith("app")); // 输出: true
trie.insert("app");
System.out.println(trie.search("app")); // 输出: true
}
6、总结
前缀树的典型应用是用于统计、排序和保存大量的字符串(但不仅限于字符串),常用于搜索引擎系统用于文本词频统计,可减少无谓的字符串比较,查询效率比哈希树高