Trie
又称前缀树或字典树,主要对字符串的存储查询
值不是直接保存在节点中,而是由节点在树中的位置决定
利用字符串的公共前缀来减少查询时间
时间复杂度和存储元素个数无关,与查询单词的长度有关 O(w)
结构
构建
/**
* Trie树 对字符串的存储查询
*
* @author Deevan
*/
public class MyTrie {
/**
* 构建节点
*/
private static class Node {
boolean isWord;
String val; //表示当前节点和之前节点组成的字符串
Map<Character, Node> next; //该结点下所有的字符对应的结点
Node() {
this("");
}
Node(String val) {
this.isWord = false;
this.val = val;
this.next = new HashMap<>();
}
}
/**
* 根节点
*/
private final Node root;
/**
* 存放单词个数
*/
private int size;
public MyTrie() {
this.size = 0;
this.root = new Node();
}
public int getSize() {
return this.size;
}
/**
* 插入字符串
*/
public void insert(String word) {
if (word == null || word.length() == 0) {
return;
}
Node curNode = root;
for (int i = 0; i < word.length(); i++) {
//拿到根节点的所有子节点
Map<Character, Node> children = curNode.next;
char c = word.charAt(i);
//不包含,添加一个节点,并让val等于上一个节点的val+字符
if (!children.containsKey(c)) {
children.put(c, new Node(curNode.val+c));
}
//更新curNode为该字符
curNode = children.get(c);
}
//curNode等于最后一个字符后,只如果该节点不是单词,才size++
if (!curNode.isWord) {
curNode.isWord = true;
this.size++;
}
}
/**
* 查找是否包含该字符串
*/
public boolean search(String word) {
if (word == null || word.length() == 0) {
return false;
}
Node curNode = this.root;
for (int i = 0; i < word.length(); i++) {
char c = word.charAt(i);
Map<Character, Node> children = curNode.next;
//不包含false 包含接着向下比较
if (!children.containsKey(c)) {
return false;
}
curNode = children.get(c);
}
//字符串中字符都比较完成后,再看这个节点是不是被isWord标记
return curNode.isWord;
}
/**
* 是否包含该前缀
* @param prefix 前缀字符串
*/
public boolean startsWith(String prefix) {
if (prefix == null || prefix.length() == 0) {
return false;
}
Node curNode = this.root;
for (int i = 0; i < prefix.length(); i++) {
char c = prefix.charAt(i);
Map<Character, Node> children = curNode.next;
if (!children.containsKey(c)) {
return false;
}
curNode = children.get(c);
}
return true;
}
/**
* 模糊匹配
*/
public boolean matchSearch(String express) {
if (express == null || express.length() == 0) {
return false;
}
return match(root, express, 0);
}
/**
* @param node 当前结点
* @param express 表达式
* @param index 表达式中要匹配字符的索引
*/
private boolean match(Node node, String express, int index) {
//递归终止条件:当匹配到字符串索引的下一位时立即结束
if (index == express.length()) {
return true;
}
char c = express.charAt(index);
//不为"."
if (c != '.') {
Map<Character, Node> children = node.next;
if (!children.containsKey(c)) {
return false;
}
return match(children.get(c), express, index + 1);
} else {
//为"."
Map<Character, Node> children = node.next;
//拿到这个节点下的所有节点和下一个字符进行比较
Set<Character> set = children.keySet();
for (Character chr : set) {
if (match(children.get(chr), express, index + 1)) {
return true;
}
}
//.下一个字符 没有匹配到
return false;
}
}
}
删除操作
- 如果单词是另外一个单词的前缀,只需将该单词最后一个结点的isWord修改成false
- 如果单词的所有字符都没有分支,删除整个单词
- 如果单词除了最后一个字符,其他的字符存在多个分支
- 对第2种情况的补充:如果单词的所有字符都没有分支,但是存在前缀作为单词的情况,例如: pan,panda,在删除 panda时,使用第三种情况进行处理(node.next.size() == 1 && node.isword)