字典树,也叫前缀树,从名称上就可以看出来,它比较适合用于前缀匹配。字典树的基本结构是一棵多叉树,每 个节点表示一个字符,从根节点到每一个叶子节点都表示一个字符串(事实上,这里不仅仅是叶子节点),准确来 说是结束节点,即每个节点都有一个标识符tag,表示当前节点是否是一个结束节点。另外每个节点都还需要某种 数据结构来保存其子节点,这里的数据结构就是不同的实现方法了,常规一点就是使用数组,当然也可以用链表 或者map。
208、LeetCode上有一些关于字典树的题目,其中第一道就是实现一棵字典树,基本功能包括:
- 插入字符串到一棵树中
- 判断字符串是否在给定的一棵树中
- 判断字符串是否是树中的某个前缀
并且这道题目,限制了假设所有的字符都是小写英文字母,也就是说,一共只有26个字符,那么每一个节点的子节点也就最 多包含26个子节点;这里常规的思路就是使用一个数组存储其子节点了,整个字典树的代码如下:
class Trie {
class TrieNode {
TrieNode[] children;
boolean isEnding;
public TrieNode() {
children = new TrieNode[26];
isEnding = false;
for (int i = 0; i < 26; i++) {
children[i] = null;
}
}
}
TrieNode root;
/** Initialize your data structure here. */
public Trie() {
root = new TrieNode();
}
/** Inserts a word into the trie. */
public void insert(String word) {
char[] array = word.toCharArray();
TrieNode cur = root;
for (int i = 0; i < array.length; i++) {
if (cur.children[array[i] - 'a'] == null) {
cur.children[array[i] - 'a'] = new TrieNode();
}
cur = cur.children[array[i] - 'a'];
}
cur.isEnding = true;
}
/** Returns if the word is in the trie. */
public boolean search(String word) {
char[] array = word.toCharArray();
TrieNode cur = root;
for (int i = 0; i < array.length; i++) {
//不含有当前节点
if (cur.children[array[i] - 'a'] == null) {
return false;
}
cur = cur.children[array[i] - 'a'];
}
//当前节点是否为是某个单词的结束
return cur.isEnding;
}
/** Returns if there is any word in the trie that starts with the given prefix. */
public boolean startsWith(String prefix) {
char[] array = prefix.toCharArray();
TrieNode cur = root;
for (int i = 0; i < array.length; i++) {
if (cur.children[array[i] - 'a'] == null) {
return false;
}
cur = cur.children[array[i] - 'a'];
}
return true;
}
}
211、请你设计一个数据结构,支持 添加新单词 和 查找字符串是否与任何先前添加的字符串匹配
这道题目不再简单的是实现一棵字典树的三大基本功能了,它的查找函数要求不太一样,待查找的单词中可能包含.号,表示可以与任意的字母 相匹配,也就是search函数需要考虑到遍历的字符可能等于.的情况。下面看一下search函数的代码,其他代码和上一道题目是一模一样的。 可以本地debug一遍流程,尤其递归的流程并不容易理解。
public boolean search(String word) {
return search(word, root);
}
private boolean search(String word, TrieNode root) {
char[] array = word.toCharArray();
TrieNode cur = root;
for (int i = 0; i < array.length; i++) {
// 对于 . , 递归的判断所有不为空的孩子
if(array[i] == '.'){
for(int j = 0;j < 26; j++){
if(cur.children[j] != null){
if(search(word.substring(i + 1),cur.children[j])){
return true;
}
}
}
return false;
}
//如果当前遍历的字符不等于.,则照常进行;如果等于.,则需要像上面的代码一样,递归的遍历不为空的子节点
// 不含有当前节点
if (cur.children[array[i] - 'a'] == null) {
return false;
}
cur = cur.children[array[i] - 'a'];
}
// 当前节点是否为是某个单词的结束
return cur.isEnding;
}