Source:https://leetcode.com/problems/implement-trie-prefix-tree/
Reference: https://leetcode.com/articles/implement-trie-prefix-tree/
Description:
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.
从leetcode官方题解中,好好学习一波Tire树(字典树、前缀树)
应用:
- autocomplete 搜索引擎自动补全
- spell checker
- IP routing IP路由 最长前缀匹配
- T9 predictive text 手机9个按键打字
- Solving word games
平衡二叉搜索树和哈希表都可以高效地进行查找,为什么还需要Trie?哈希表可以 O ( 1 ) O(1) O(1)查找,但是在下面两个问题里并不有效:
- 查找所有具有公共前缀的key
- 按字典序枚举一个字符串集合
此外,哈希表在数据规模很大时会有不少冲突,使得查找时间线性增长, O ( n ) O(n) O(n)。在很多key都有相同前缀的情况下,Trie比哈希表更节省空间。而且Trie的时间复杂度是 O ( m ) O(m) O(m),m是key的长度。而平衡树的时间是 O ( m lg n ) O(m\lg n) O(mlgn)。
结点结构
Trie是有根树,结点结构设计:
令R表示字符集大小,比如只有小写英文字母的话就是26。每个结点最多有R个指向孩子的link,还有一个bool变量isEnd表示是否是key的最后一个字母。
class TrieNode {
// R links to node children
private TrieNode[] links;
private final int R = 26;
private boolean isEnd;
public TrieNode() {
links = new TrieNode[R];
}
public boolean containsKey(char ch) {
return links[ch -'a'] != null;
}
public TrieNode get(char ch) {
return links[ch -'a'];
}
public void put(char ch, TrieNode node) {
links[ch -'a'] = node;
}
public void setEnd() {
isEnd = true;
}
public boolean isEnd() {
return isEnd;
}
}
插入
在Trie中search link,对应每个字母,如果存在,沿树下行,进行到下一个字母;如果不存在,新建一个结点,确立父子关系,继续下一个字母。
直到到了最后一个字母,isEnd标记为true。
class Trie {
private TrieNode root;
public Trie() {
root = new TrieNode();
}
// Inserts a word into the trie.
public void insert(String word) {
TrieNode node = root;
for (int i = 0; i < word.length(); i++) {
char currentChar = word.charAt(i);
if (!node.containsKey(currentChar)) {
node.put(currentChar, new TrieNode());
}
node = node.get(currentChar);
}
node.setEnd();
}
}
时空复杂度都是 O ( m ) O(m) O(m)。
查找
如果待查找key的字母到最后一个了而且isEnd是true,那就是存在的。有两种情况不存在:
- key里还有字母未匹配,但是Trie已经没有路径了。
- key里没有字母了,但是当前的Trie结点isEnd是false,说明待查找的key是一个前缀。
// search a prefix or whole key in trie and
// returns the node where search ends
private TrieNode searchPrefix(String word) {
TrieNode node = root;
for (int i = 0; i < word.length(); i++) {
char curLetter = word.charAt(i);
if (node.containsKey(curLetter)) {
node = node.get(curLetter);
} else {
return null;
}
}
return node;
}
// Returns if the word is in the trie.
public boolean search(String word) {
TrieNode node = searchPrefix(word);
return node != null && node.isEnd();
}
时间复杂度 O ( m ) O(m) O(m),空间 O ( 1 ) O(1) O(1)。
查找prefix
基本与查找是相同的,一点区别在于,到达待查找prefix的最后一个字母时直接返回true,不用考虑isEnd。
// Returns if there is any word in the trie
// that starts with the given prefix.
public boolean startsWith(String prefix) {
TrieNode node = searchPrefix(prefix);
return node != null;
}
时间复杂度 O ( m ) O(m) O(m),空间 O ( 1 ) O(1) O(1)。