1.Trie是什么?
Trie,取自“retrieval”检索,又称前缀树或字典树,是一种有序树,,用于保存关联数组,其中的键通常是字符串。与二叉查找树不同,键不是直接保存在节点中,而是由节点在树中的位置决定。一个节点的所有子孙都有相同的前缀,也就是这个节点对应的字符串,而根节点对应空字符串。
In computer science, a trie, also called digital tree or prefix tree, is a kind of search tree—an ordered tree data structure used to store a dynamic set or associative array where the keys are usually strings. Unlike a binary search tree, no node in the tree stores the key associated with that node; instead, its position in the tree defines the key with which it is associated; i.e., the value of the key is distributed across the structure. All the descendants of a node have a common prefix of the string associated with that node, and the root is associated with the empty string. (from wikipedia)
2.有什么性质?
从图中可以明确看出:
a.根节点为空
b.节点中没有字符,字符是在路径上的。经过即得。
c.每个节点下方,最多有26中可能,每个节点分支节点都是独特的。
d.为了使用方便,通常为组成有效单词的节点“染色”,标记“isWord”。
class TrieNode{
TrieNode[] children=new TrieNode[26];
boolean isWord;
}
3.有什么好处?
优点是速度快,Trie树的查询复杂度在最差情况下是 O(length(input)),Hash 只能做到在平均情况下 O(length(input))
Trie的应用,通过建树的方式,用空间换取了时间。如此一来,搜索中出现大量重复的单词,不用每次一一匹配每一个char。
不过,
当字符串数量多的时候,比二叉树和哈希表更省空间。 可以做前缀比较,重要功能之一是用来做输入提示。(from zhihu)
比如搜索dog和dogs,d-o-g部分不用重复存储。
insert/delete/find都是o(m), m是word的长度。
它的核心思想就是通过最大限度地减少无谓的字符串比较,使得查询高效率,即「用空间换时间」,再利用共同前缀来提高查询效率。
from知乎
5.如何build Trie?
There are two ways: to build the TrieNode
one use arrays: TrieNode[]children
node.children[w.charAt(i)-‘a’];
one use map: Map<char,TrieNode>
node.children.get(char);可以查看是否存在相应的TrieNode
class TrieNode{
TrieNode[] children=new TrieNode[26];
boolean isWord;
}
private TrieNode buildTrie(String[] words){
TrieNode root=new TrieNode();
for(String w:words){
TrieNode cur=root;
for(int i=0;i<w.length();i++){
TrieNode next=cur.children[w.charAt(i)-'a'];
if(next==null){
//next在图中不存在
next=new TrieNode();//扔一块空石头
cur.children[w.charAt(i)-'a']=next;//安插入队
}
cur=next;//无论是新加一个node还是原来途中有,都需要往下走
}
cur.isWord=true;
}
return root;
}
//https://leetcode.com/problems/implement-trie-prefix-tree/discuss/58832/AC-JAVA-solution-simple-using-single-array/200487/
利用Map来建设Trie
class TrieNode {
Map<Character, TrieNode> children = new HashMap();
boolean word = false;
public TrieNode() {
}
}
class WordDictionary {
TrieNode root;
/** Initialize your data structure here. */
public WordDictionary() {
root = new TrieNode();
}
/** Adds a word into the data structure. */
public void addWord(String word) {
TrieNode node = root;
for (char ch : word.toCharArray()) {
if (!node.children.containsKey(ch)) {
//next在图中不存在
node.children.put(ch, new TrieNode());//安插入队
}
node = node.children.get(ch);//往下走
}
node.word = true;
}
}
Trie树的插入操作
Trie树的插入操作很简单,其实就是将单词的每个字母逐一插入 Trie树。插入前先看字母对应的节点是否存在,存在则共享该节点,不存在则创建对应的节点。比如要插入新单词cook,
就有下面几步:
插入第一个字母 c,发现 root 节点下方存在子节点 c, 则共享节点 c
插入第二个字母 o, 发现 c 节点下方存在子节点 o,则共享节点 o
插入第三个字母 o,发现 o 节点下方不存在子节点 o,则创建子节点 o
插入第四个字母 k,发现 o 节点下方不存在子节点 k,则创建子节点 k
至此,单词 cook 中所有字母已被插入 Trie树 中,然后设置节点 k 中的标志位,标记路径 root->c->o->o->k这条路径上所有节点的字符可以组成一个单词cook。
Trie树的查找操作
在 Trie 树中查找一个字符串的时候,比如查找字符串 code,可以将要查找的字符串分割成单个的字符 c,o,d,e,然后从 Trie 树的根节点开始匹配。
如果要查找的是字符串cod(鳕鱼)呢?
还是可以用上面同样的方法,从根节点开始,沿着某条路径来匹配,如图所示,绿色的路径,是字符串cod匹配的路径。但是,路径的最后一个节点「d」并不是橙色的,并不是单词标志位,所以cod字符串不存在!
条件:这个char存在路径中,是单词的最后一个char,标志位true,
class TrieNode{
Map<Character, TrieNode> children = new HashMap();
boolean isword