Trie 字典树 前缀树
一、什么是Trie
多叉树,专为字符串来设计,每一个Node还是一个映射。
二、实现Trie
由于在Trie中,指针next由存在映射Map,因此这里通过TreeMap进行构建。
- 实现Trie基本结构及添加单词的操作(注意添加单词的方法中的逻辑)
import java.util.TreeMap;
public class Trie {
private class Node{
public boolean isWord;
public TreeMap<Character,Node> 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;
}
//获得Trie中存储的单词数量
public int getSize() {
return size;
}
//向Trie中添加一个新的单词word
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) { //向Trie中添加单词时,如果添加的单词很有可能已经在Trie中了,因此在进行size++之前还需要判断
//当前来到的节点以前是false的
cur.isWord = true;
size ++;
}
}
}
- Trie查询元素
注意结尾不能够直接返回true
//查询单词word是否在Trie中
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);
}
/* 不能直接返回True的原因:比如现在Trie中存在有pandas这个单词,但是我们现在要去查pan这个单词。如果直接
返回True,则表明此时是可以查找到的,但是实际上Trie中是没有这个单词的。表现在cur.isWord为false。如果
cur.isWord为true相当于有这个单词,否则的话表示没有*/
return cur.isWord;
}
- Trie和前缀搜索
与上述查找单词word是否在Trie中很像,只需修改结尾。
//查询是否在Trie中有单词以prefix为前缀
public boolean isOrefix(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;
}
三、LeetCode
LeetCode 211:添加与搜索单词 - 数据结构设计
- 简单的模式匹配:对于实现’.'这种任意字符的匹配方式(通配符),则应该遍历在节点d之下的所有节点的内容,查找是否有满足d…r的格式的存在情况。
- 代码实现
import java.util.TreeMap;
class WordDictionary {
private class Node{
public boolean isWord;
public TreeMap<Character,Node> next;
public Node(boolean isWord) {
this.isWord = isWord;
next = new TreeMap<>();
}
public Node() {
this(false);
}
}
private Node root;
/** Initialize your data structure here. */
public WordDictionary() {
root = new Node();
}
/** Adds a word into the data structure. */
public void addWord(String word) {
Node cur = root; //为了防止将root的结构改变
for(int i=0;i<word.length();i++) {
if(cur.next.get(word.charAt(i))==null) {
cur.next.put(word.charAt(i),new Node());
}
cur = cur.next.get(word.charAt(i));
}
cur.isWord = true;
}
/** Returns if the word is in the data structure. A word could contain the dot character '.' to represent any one letter. */
public boolean search(String word) {
return match(root,word,0); //从字符串的第一个值开始搜索
}
private boolean match(Node node,String word,int index) { //以字符串word作为模式进行搜索
if(index == word.length()) {
return node.isWord; //考虑完整个字符串,此时如果node.isWord==true表明这个节点是一个单词,则表明找到
}
char c = word.charAt(index);
//分情况讨论:c是普通的字母还是通配符
if(c!='.') {
if(node.next.get(c)==null) {
return false;
}
return match(node.next.get(c),word,index+1);
}else {
for(char nextChar:node.next.keySet()) {
if(match(node.next.get(nextChar),word,index+1)) {
return true;
}
}
return false;
}
}
}
LeetCode 677:键值映射
代码实现
class MapSum {
private class Node{
public int value;
public TreeMap<Character,Node> next;
public Node(int value) {
this.value = value;
next = new TreeMap<>();
}
public Node() {
this(0);
}
}
private Node root;
/** Initialize your data structure here. */
public MapSum() {
root = new Node();
}
public void insert(String word, int val) {
Node cur = root; //为了防止将root的结构改变
for(int i=0;i<word.length();i++) {
if(cur.next.get(word.charAt(i))==null) {
cur.next.put(word.charAt(i),new Node());
}
cur = cur.next.get(word.charAt(i));
}
cur.value = val;
}
public int sum(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 0;
}
cur = cur.next.get(c);
}
//递归实现找到所有以cur的根节点的总和
return sum(cur);
}
private int sum(Node node) {
if(node.next.size() == 0) {
return node.value;
}
int res = node.value;
for(char c:node.next.keySet()) {
res += sum(node.next.get(c));
}
return res;
}
}