线段树、Trie和并查集02:Trie
Trie,又称字典树、前缀树,是一种N叉树
Trie是一种专门为字典设计的数据结构,通常只用来处理字符串;而之前的Map更适合称为映射而不是字典,因为其存储的对象不一定是字符串
如果用TreeMap和Trie来查询字符串,TreeMap的时间复杂度为O(logn),而Trie的时间复杂度和总数n无关,只和字符串的长度有关,每个节点只存储一个字符
在n个条目中查询字符串 | 时间复杂度 |
---|---|
TreeMap | O(logn) |
Trie | O(w) |
实现字典树
import java.util.TreeMap;
/**
* 字典树一般存储的都是字符串,因此不使用泛型
*/
class Trie{
private class Node{
/**
* 每个节点需要一个标志isWord来判断是否是一个字符串的结尾,因为很多字符串有重叠部分
* 每个节点,其子节点的数目是不确定的,因此定义为TreeMap类型,每个节点的子节点都是一个集合,也即N叉树
*/
public boolean isWord;
public TreeMap next;
public Node(boolean isWord){
this.isWord = isWord;
next = new TreeMap<>();
}
public Node(){
this(false);
}
}
private Node root;
private int size;
public Trie(){
root = new Node();
size = 0;
}
public int getSize(){
return size;
}
/**
* 增
* 从根节点出发,先判断子节点集合中有没有当前要放入的字符,没有就添加,然后继续向下寻找(寻找下一个符合的子节点使用Map类的get()方法)
* 因为字符串可能重复,因此将新字符串的isWord设置为true,此时才能size加1
*/
public void add(String string){
Node cur = root;
for (int i = 0; i < string.length(); i++) {
char c = string.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++;
}
}
/**
* 查
*/
public boolean contains(String string){
Node cur = root;
for (int i = 0; i < string.length(); i++) {
char c = string.charAt(i);
if (cur.next.get(c) == null){
return false;
}
else {
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;
}
else {
cur = cur.next.get(c);
}
}
return true;
}
}
字典树的局限性
最大的问题:空间消耗!每个节点最少有26个子节点,都使用一个TreeMap集合来实现,空间消耗很大
优化的方向:压缩字典树、三分搜索树、后缀树
压缩字典树(Compressed Trie)
将字母合并为词缀
三分搜索树(Ternary Search Trie)
每个节点有三个孩子,分别小于、等于和大于自身