1.字典树的概述
首先我们需要先知道什么是trie,Trie树,又称字典树,单词查找树或者前缀树,是一种用于快速检索的多叉树结构,如英文字母的字典树是一个26叉树,数字的字典树是一个10叉树。
Trie一词来自retrieve,发音为/tri:/ “tree”,也有人读为/traɪ/ “try”。
Trie树可以利用字符串的公共前缀来节约存储空间,进而可以减少查询时间,查询效率比哈希表高。
2.字典树的基本思路
字典树的基本思路是,通过树这种数据结构,读入文本的时候同步建立树,每个节点通过一个指针数组保存了子树的指针,指针数组的下标存储了单词的信息,将每个单词的公共前缀都保存在同一条路径上,尽可能的节省了空间。同时因为一颗平衡二叉树的查找效率很高,因此字典树的查找效率很高,为O(logN)。
3.性质
(1)根节点不包含字符,除根节点外每一个节点都只包含一个字符;
(2)从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串;
(3)每个节点的所有子节点包含的字符都不相同。
单词列表为”apps”,”apply”,”apple”,”append”,”back”,”backen”以及”basic”对应的字母树可以是如下图所示。
例如,保存”apple”和 “apply”时,由于它们的前四个字母是相同的,所以希望它们共享这些字母,而只对剩下的部分进行分开存储。可以很明显地发现,字母树很好地利用了串的公共前缀,节约了存储空间。
4.应用
(1)串的快速检索
给出N个单词组成的熟词表,以及一篇全用小写英文书写的文章,请你按最早出现的顺序写出所有不在熟词表中的生词。
在这道题中,我们可以用数组枚举,用哈希,用字典树,先把熟词建一棵树,然后读入文章进行比较,这种方法效率是比较高的。
(2)“串”排序
给定N个互不相同的仅由一个单词构成的英文名,让你将他们按字典序从小到大输出用字典树进行排序,采用数组的方式创建字典树,这棵树的每个结点的所有儿子很显然地按照其字母大小排序。对这棵树进行先序遍历即可。
(3)最长公共前缀
对所有串建立字典树,对于两个串的最长公共前缀的长度即他们所在的结点的公共祖先个数,于是,问题就转化为当时公共祖先问题。
上面就是关于字典树的相关知识,下面我们就对lintcode上的两题进行解答。
第一题–实现Trie
题目描述:
实现一个 Trie,包含 insert, search, 和 startsWith 这三个方法。
注意事项:
你可以假设所有的输入都是小写字母a-z。
样例:
insert(“lintcode”)
search(“code”) // return false
startsWith(“lint”) // return true
startsWith(“linterror”) // return false
insert(“linterror”)
search(“lintcode) // return true
startsWith(“linterror”) // return true
代码详解:
const int MAX=26;
class TrieNode{
public:
int count;
TrieNode *child[MAX];
TrieNode(){
for(int i=0;i<26;i++){
child[i]=NULL;
}
count=0;
}
};
class Trie {
public:
Trie() {
// do intialization if necessary
root=new TrieNode();
}
/*
* @param word: a word
* @return: nothing
*/
void insert(string &word) {
// write your code here
if(word.size()==0){
return;
}
int len=word.size();
TrieNode *t=root;
int i=0;
while(i<len){
if(t->child[word[i]-'a']==NULL){
TrieNode *temp=new TrieNode();
t->child[word[i]-'a']=temp;
t=t->child[word[i]-'a'];
}else{
t=t->child[word[i]-'a'];
}
i++;
}
t->count=1;
}
/*
* @param word: A string
* @return: if the word is in the trie.
*/
bool search(string &word) {
// write your code here
search1(word,root,0);
}
bool search1(string &word,TrieNode *p,int i)
{
if(i==word.size()){
return p->count;
}
return p->child[word[i]-'a']&&search1(word,p->child[word[i]-'a'],i+1);
}
/*
* @param prefix: A string
* @return: if there is any word in the trie that starts with the given prefix.
*/
bool startsWith(string &prefix) {
// write your code here
TrieNode *p = root;
for (auto &a : prefix) {
int i = a - 'a';
if (!p->child[i]) return false;
p = p->child[i];
}
return true;
}
private:
TrieNode*root;
};
第二题–单词的添加与查找
题目描述:
设计一个包含下面两个操作的数据结构:addWord(word), search(word)
addWord(word)会在数据结构中添加一个单词。而search(word)则支持普通的单词查询或是只包含.和a-z的简易正则表达式的查询。
一个 . 可以代表一个任何的字母。
注意事项:
你可以假设所有的单词都只包含小写字母 a-z。
样例:
addWord(“bad”)
addWord(“dad”)
addWord(“mad”)
search(“pad”) // return false
search(“bad”) // return true
search(“.ad”) // return true
search(“b..”) // return true
代码详解:
const int MAX_CHILD=26;
class TrieNode{
public:
int count;//判断是字符串而不是字符串前缀
TrieNode *child[MAX_CHILD];
TrieNode(){
for(int i=0;i<26;i++){
child[i]=NULL;
}
count=0;
}
};
class WordDictionary {
public:
/*
* @param word: Adds a word into the data structure.
* @return: nothing
*/
WordDictionary(){
root=new TrieNode();
}
void addWord(string &word) {
// write your code here
if(root==NULL||word.size()==0){
return ;
}
int len=word.size();
TrieNode *t=root;
int i=0;
while(i<len){
if(t->child[word[i]-'a']==NULL){
TrieNode*temp=new TrieNode();
t->child[word[i]-'a']=temp;
t=t->child[word[i]-'a'];
}else{
t=t->child[word[i]-'a'];
}
i++;
}
t->count=1;
}
/*
* @param word: A word could contain the dot character '.' to represent any one letter.
* @return: if the word is in the data structure.
*/
bool search(string &word) {
// write your code here
search(word,root,0);
}
bool search(string &word,TrieNode *p,int i)
{
if(i==word.size()){
return p->count;
}
if(word[i]=='.'){
for(auto a : p->child){
if(a && search(word,a,i+1)){
return true;
}
}
return false;
}else{
return p->child[word[i]-'a']&&search(word,p->child[word[i]-'a'],i+1);
}
}
private:
TrieNode *root;
};
参考博客:http://blog.csdn.net/sunnyyoona/article/details/43900425