Trie 前缀树
Trie 是一种特殊的数据结构,可以看作是 多叉树 与 链表 的结合体,其主要作用是用于 前缀查询 与 模式匹配。
什么是 Trie?
Trie 是一种特殊的数据结构,可以看作是 多叉树 与 链表 的结合体,它长这样:

每一个节点都含有多个指针,指向下一个节点,我们可以用一个Map 来实现这种功能。另外蓝色节点代表这个节点是一个单词的末尾。比如 "p" -> "a" -> "n" 中的 "n" 就是蓝色节点,这代表当前 Trie 中包含 “pan" 这个单词。而"c" -> "a" -> "t" 中的 "t" 是红色节点,则代表当前 Trie 中不包含 “cat" 这个单词。
Trie的性能特性
在现实中,需要存储的字符的种类是有限的(比如英文大小写,数字,还有一些特殊符号),在每一个节点的分叉支路也是有限的,那么我们可以得出以下结论: 在 Trie 中增加,查找 某个字符串的时间复杂度均为 O(h), h 为字符串长度,与Trie 中字符串的个数无关。
实现一个 Trie
构造方法
构造方法非常简单,isWord 表示是否为字符串结尾,true代表是结尾,也就是上图中的蓝色节点。
public class Trie {
private class Node{
public boolean isWord;
public TreeMap<Character, Node> next;
public Node(boolean isWord ) {
this.isWord = isWord;
next = new TreeMap<Character, Node>();
}
public Node( ) {
this(false);
}
}
private Node root;
private int size;
public Trie(){
root=new Node();
size=0;
}
}
获取字符串个数
public int getSize(){
return size;
}
添加字符串
添加字符串非常简单,只需要依次按照字符串的每一位,在Trie中遍历即可,遇到不曾出现的字符,就新建一个节点。
public void add(String word){
Node cur = root;
for(int i =0; i< word.length(); i++){
char c= word.charAt(i);
if (cur.next.get(c)==null){
cur.next.put(c, new Node());
}
cur=cur.next.get(c);
}
if (!cur.isWord){
cur.isWord=true;
size++;
}
}
特别的是第12-14行,在最后一个字符时需要判断这个字符串是不是之前就已经存在了,若之前不存在才需要将 isWord 设置为 true, size++。否则 size 会多加1, 出现bug。
查询与前缀查询
查询 与前缀 查询的代码逻辑与 添加 操作类似,都是在 Trie 中一个个查询 字符串的每一位。去别在于 contains 需要确认最后一个字符对应的节点的 is.Word 是否为真,而前缀查询则不需要。
public boolean contains(String word){
Node cur = root;
for(int i =0; i< word.length(); i++){
char c= word.charAt(i);
if (cur.next.get(c)==null){
return false;
}
cur=cur.next.get(c);
}
return cur.isWord;
}
public boolean isPrefix(String prefix){
Node cur = root;
for(int i =0; i< prefix.length(); i++){
char c= prefix.charAt(i);
if (cur.next.get(c)==null){
return false;
}
cur=cur.next.get(c);
}
return true;
}
模式匹配
Trie 另一个重要应用是模式匹配,比如查询”p...a“ 是否存在,其中的 "."代表可以为任意字符,返回 true 或 false。需要用到树的深度优先算法,我们可以用递归离开完成:
public boolean match(String word){
return match(root,word,0);
}
private boolean match(Node node, String word, int depth){
if (depth==word.length()){
return node.isWord;
}
char c=word.charAt(depth);
if (c!='.'){
if (node.next.get(c)==null){
return false;
}else{
return match(node.next.get(c),word,depth+1);
}
}else {
for (char nextChar : node.next.keySet()){
return match(node.next.get(nextChar),word,depth+1);
}
return false;
}
}
测试一下
简单测试一下我们写的 Trie
public class TrieTest {
public static void main(String[] args) {
Trie trieSet=new Trie();
trieSet.add("cater");
trieSet.add("dog");
trieSet.add("deer");
trieSet.add("pan");
trieSet.add("panda");
System.out.println(trieSet.contains("cat"));
System.out.println(trieSet.isPrefix("cat"));
System.out.println(trieSet.contains("pan"));
System.out.println(trieSet.isPrefix("pan"));
System.out.println(trieSet.match("de.r"));
System.out.println(trieSet.match("d..d"));
}
}
输出结果:
false
true
true
true
true
false
Last updated
Was this helpful?