题目
Implement a trie with insert
, search
, and startsWith
methods.
Example:
Trie trie = new Trie();
trie.insert("apple");
trie.search("apple"); // returns true
trie.search("app"); // returns false
trie.startsWith("app"); // returns true
trie.insert("app");
trie.search("app"); // returns true
Note:
- You may assume that all inputs are consist of lowercase letters
a-z
. - All inputs are guaranteed to be non-empty strings.
十分钟尝试
一个新的数据结构,实现一个字典树/前缀树,是一个多叉树。补充一下知识点:
上图是一棵Trie树,表示了关键字集合{“a”, “to”, “tea”, “ted”, “ten”, “i”, “in”, “inn”} 。从上图可以归纳出Trie树的基本性质:
- 根节点不包含字符,除根节点外的每一个子节点都包含一个字符。
- 从根节点到某一个节点,路径上经过的字符连接起来,为该节点对应的字符串。
- 每个节点的所有子节点包含的字符互不相同。
Trie树的核心思想是空间换时间,利用字符串的公共前缀来减少无谓的字符串比较以达到提高查询效率的目的。
相比于hash
- 如果根据共同前缀寻找所有的key,hash性能不好,因为hash碰撞
- 按照字典排序列举字符串
- 随着hash size增加,出现碰撞概率加大,影响性能
平衡树稍后对比
应用
- 字符串查找:
检索/查询功能是Trie树最原始的功能。思路就是从根节点开始一个一个字符进行比较:如果沿路比较,发现不同的字符,则表示该字符串在集合中不存在。如果所有的字符全部比较完并且全部相同,还需判断最后一个节点的标志位(标记该节点是否代表一个关键字)
- 词频统计:为了实现词频统计,我们修改了节点结构,用一个整型变量
count
来计数。对每一个关键字执行插入操作,若已存在,计数加1,若不存在,插入后count
置1 - 字符串排序:思路也很简单:遍历一次所有关键字,将它们全部插入trie树,树的每个结点的所有儿子很显然地按照字母表排序,然后先序遍历输出Trie树中所有关键字即可
- 前缀匹配:
找出一个字符串集合中所有以
ab
开头的字符串。我们只需要用所有字符串构造一个trie树,然后输出以a->b->
开头的路径上的关键字即可。trie树前缀匹配常用于搜索提示。如当输入一个网址,可以自动搜索出可能的选择。当没有完全匹配的搜索结果,可以返回前缀最相似的可能。
前缀树节点结构
1. 子节点的指针数组,指向所有的子节点,因为是多叉树,不同于二叉树,只有left和right
2 每个节点包含一个boolean值的field,表示是否是key的结束,或者只是一个prefix。
我看了答案的思路后,自己写了一般,需要注意的一点是,search和startPrefix的不同,search需要判断最后的节点是不是end,也就是叶子节点,而startPrefix不需要。其他我的代码命名更加适合自己的理解。
class TrieNode{
private TrieNode[] childrens;
private boolean isEnd;
public TrieNode(){
childrens=new TrieNode[26];
}
public boolean hasChild(char ch){
return childrens[ch-'a']!=null;
}
public void addChild(char ch){
childrens[ch-'a']=new TrieNode();
}
public TrieNode getChild(char ch){
return childrens[ch-'a'];
}
public void setEnd(){
isEnd=true;
}
public boolean isEnd(){
return isEnd;
}
}
class Trie {
private TrieNode root;
/** Initialize your data structure here. */
public Trie() {
root=new TrieNode();
}
/** Inserts a word into the trie. */
public void insert(String word) {
TrieNode currNode=root;
if(word==null||word.length()==0){
return;
}
for(int i=0;i<word.length();i++){
char currChar=word.charAt(i);
if(!currNode.hasChild(currChar)){
currNode.addChild(currChar);
}
TrieNode nextNode=currNode.getChild(currChar);
currNode=nextNode;
}
currNode.setEnd();
}
/** Returns if the word is in the trie. */
public boolean search(String word) {
TrieNode currNode=root;
if(word==null||word.length()==0){
return false;
}
for(int i=0;i<word.length();i++){
char currChar=word.charAt(i);
if(!currNode.hasChild(currChar)){
return false;
}
TrieNode nextNode=currNode.getChild(currChar);
currNode=nextNode;
}
return currNode.isEnd();
}
/** Returns if there is any word in the trie that starts with the given prefix. */
public boolean startsWith(String prefix) {
TrieNode currNode=root;
if(prefix==null||prefix.length()==0){
return false;
}
for(int i=0;i<prefix.length();i++){
char currChar=prefix.charAt(i);
if(!currNode.hasChild(currChar)){
return false;
}
TrieNode nextNode=currNode.getChild(currChar);
currNode=nextNode;
}
return true;
}
}
/**
* Your Trie object will be instantiated and called as such:
* Trie obj = new Trie();
* obj.insert(word);
* boolean param_2 = obj.search(word);
* boolean param_3 = obj.startsWith(prefix);
*/