(一) 定义
字典树(Trie): 又称前缀树, 是一种多叉树形结构, 是一种哈希树的变种. 查询每个条目的时间复杂度和字典树中一共有多少个条目无关, 与查询的字符串的长度相关(O(w)).字典树满足已下性质:
- 根节点不包含字符, 除根节点外每一个节点都只包含一个字符和一个单词结尾标识
- 从根节点到某一节点, 路径上经过的字符连接起来, 为该节点对应的字符串
- 每个节点的所有子节点包含的字符都不相同
Trie的核心思想是空间换时间: 利用字符串的公共前缀来降低查询时间的开销以达到提高效率的目的。
(二) 自定义字典树
1.字典树的基本结构
public class Tire {
/**
* 结点内部类
*
* @author Administrator
*
*/
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 Tire() {
this.root = new Node();
this.size = 0;
}
/**
* 获取字典树存储的单词数量
*
* @return
*/
public int getSize() {
return size;
}
}
2.字典树的添加操作
/**
* 向字典树添加一个新的单词word
*
* @param word
*/
public void add(String word) {
Node cur = root;
for (int i = 0; i < word.length(); i++) {
char c = word.charAt(i);
// 当前结点的下一个结点映射是否存在c这个字符的映射, 不存在添加
if (cur.next.get(c) == null) {
cur.next.put(c, new Node());
}
cur = cur.next.get(c);
}
// 判断添加的元素word是否存在
if (!cur.isWord) {
cur.isWord = true;
size++;
}
}
3.字典树的查询操作
- 查询单词word是否在字典树中
public boolean contains(String word) {
Node cur = root;
for (int i = 0; i < word.length(); i++) {
char c = word.charAt(i);
// 当前结点的下一个结点映射是否存在c这个字符的映射
if (cur.next.get(c) == null) {
return false;
}
cur = cur.next.get(c);
}
// 判断当前结点的isWord是否为单词结尾标识
return cur.isWord;
}
- 查询是否在字典树中有单词以prefix为前缀
public boolean isPrefix(String prefix) {
Node cur = root;
for (int i = 0; i < prefix.length(); i++) {
char c = prefix.charAt(i);
// 当前结点的下一个结点映射是否存在c这个字符的映射
if (cur.next.get(c) == null) {
return false;
}
cur = cur.next.get(c);
}
return true;
}
测试
public static void main(String[] args) {
Tire tire = new Tire();
String[] arr = new String[] {"cat", "dog", "deer", "panda", "pan", "pan"};
for (int i = 0; i < arr.length; i++) {
tire.add(arr[i]);
}
System.out.println(tire.getSize());
System.out.println(tire.contains("pan"));
System.out.println(tire.contains("panda"));
System.out.println(tire.contains("do"));
System.out.println(tire.isPrefix("do"));
}
4.字典树的删除操作
/**
* 删除word, 返回是否删除成功, 递归算法
*
* @param word
* @return
*/
public boolean remove(String word){
if(word.equals(""))
return false;
return remove(root, word, 0);
}
/**
* 在以Node为根的Trie中删除单词word[index...end),返回是否删除成功, 递归算法
*
* @param node
* @param word
* @param index
* @return
*/
private boolean remove(Node node, String word, int index) {
// 递归终止条件: 找到最后一个字符所对应的结点
if (index == word.length()) {
// 判断是否为一个单词
if (!node.isWord) {
return false;
}
// 设置单词结尾标识为false
node.isWord = false;
size--;
return true;
}
char c = word.charAt(index);
if (node.next.get(c) == null) {
return false;
}
boolean ret = remove(node.next.get(c), word, index + 1);
// 以上操作是设置单词结尾标识, 下面的才是真正的删除代码
Node nextNode = node.next.get(c);
if(!nextNode.isWord && nextNode.next.size() == 0) {
node.next.remove(word.charAt(index));
}
return ret;
}