Trie
Trie树
Trie树, 又称作字典树,前缀树(prefix tree),单词查找树或者键树. 它是一种树结构,主要用于检索字符串数据集中的键。
它是一种非常有用的数据结构,应用在多个场景,例如搜索框中的自动补全Autocomplete,单词的拼写检查Spell Checker, IP 路由匹配IP routing(Longest prefix matching) 等等。
Trie树与其他数据结构比较
对于单词的查找匹配,我们还有其他方式解决例如平衡树(Balanced Tree)和哈希表(Hash Table).在何种情况下,选择Trie树比较合适呢?在一般情况下,哈希表具有在查找一个键(key)时O(1)的时间复杂度(Time Complexity),但是在以下集中情况中,它的时间复杂度会上升:
- 查找所有具有相同前缀的键(key)
- 以字典顺序列举字符串数据集
Trie树优于Hash Table的另外一个重要原因是随着散列表的大小增加,有大量的哈希冲突,搜索时间复杂度可能会恶化为O(n)。当存储具有相同前缀的多个键时,Trie可以使用比哈希表少的空间。在这种情况下,使用Trie只有O(m)时间复杂度,其中m是键长度。寻找一个平衡树中的一个键成本O(mlogn)时间复杂度。
Trie 节点数据结构
Trie是一个有根树。一般情况下,应该包括以下的域:
- 与其孩子的R链路的最大值,其中每个链接对应于来自数据集字母表的R个字符值之一。在本文中,我们假设R为26,小写拉丁字母的数量。
- 布尔字段,指定节点是否对应于待匹配字符串的结尾或者是一个待匹配字符串的前缀。
注: 本文代码示例使用Java语言
public class TrieNode {
// R links to node children
private TrieNode[] links;
private final int R = 26;
private boolean isEnd;
public TrieNode(){
links = new TrieNode[R];
}
public boolean containsKey(char ch){
return links[ch - 'a'] != null;
}
public TrieNode get(char ch){
return links[ch - 'a'];
}
public void put(char ch, TrieNode node){
links[ch - 'a'] = node;
}
public void setEnd(){
this.isEnd = true;
}
public boolean isEnd(){
return isEnd;
}
}
//树结构
public class TrieTree{
private TrieNode root;
public TrieTree(){
root = new TrieNode();
}
// Inserts a word into the trie tree
public void insert(String word){
...
}
//Search for a word is in the trie tree or not
public boolean search(String word){
...
}
//Search for a word is in the trie treee or not that starts with given prefix.
public boolean startsWith(String prefix){
...
}
}
在Trie树中查找单词
每个待查找串都在树中表示为从根到内部节点或叶的路径。在查询时,我们从根节点开始,依次查找当前字符到下一个字符之间的链接是否存在。
如下两个示例:
- 链接存在,顺着链接路径寻找到下一个节点的链接是否存在。
- 链接不存在,如果待匹配字符串中没有下一个字符,并且当前节点被标记为是一个串的结尾, 返回true, 否则,这里有两种情况发生:
- 待匹配字符串已经到尾部,但是Trie树中当前节点并没有被标记为可以时一个串的结尾。也就是说这个待匹配串时Trie树中另外一个串的前缀;
- 待匹配字符串中仍有字符存在,但是Trie树中没有通向下一个字符的链接,也即是说Trie树中不存在该待匹配字符串匹配。
如图所示:
![]()
代码实现
private TrieNode searchPrefix(String word){
TrieNode node = root;
for(int i = 0; i < word.length(); i ++){
char ch = word.charAt(i);
if(node.containsKey(ch)){
node = node.get(ch);
}else{
return null;
}
}
return node;
}
//Search for a word in the trie tree
public boolean search(String word){
TrieNode node = searchPrefix(word);
//Maybe the node is not the end of a word
return node != null && node.isEnd();
}
- 时间复杂度 :O(m)
- 空间复杂度 :O(1)
在Trie树中查找串前缀
该方法与我们用于搜索一个特技中的关键字的方法非常相似。从根目录中遍历Trie树,直到没有键入前缀中的字符,或者不可能使用当前的关键字继续使用Trie树中的路径。与上面提到的关键算法的唯一区别是,当待匹配前缀结束时,返回true;不需要考虑当前节点的isEnd标记,因为我们正在搜索一个键的前缀,而不是整个串。
如下图所示:
![]()
代码实现:
//Search for a key prefix of a word in a trie tree
public boolean startWith(String word){
TrieNode node = searchPrefix(word);
return node != null;
}
- 时间复杂度: O(m)
- 空间复杂度:O(1)