一、背景
摘抄自别人的博客“trie树,即字典树,又称单词查找树或键树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计和排序大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:最大限度地减少无谓的字符串比较,查询效率比哈希表高。”
总体来讲,前缀树的构造过程,通过不断插入新的字符串来丰富这棵26叉树。强调注意这里是26叉树,因为每一个英文字符串中下一个字母都只能是a-z中的一种可能。
前缀树的功能很强大,可以做文本词频统计,例如我们在搜索框中的搜索提示,就可以利用前缀树实现。因此,前缀树基本的操作是字符串的插入,搜索,删除,查找前缀等。
二、分析
这个题就是要求实现前缀树的insert,search以及startwith功能。具体的功能含义在注释中给出了。我们主要对题目的输入解读一下,不然debug的时候完全不知道如何入手。
["Trie","insert","search","search","startsWith","insert","search"]
[[],["apple"],["apple"],["app"],["app"],["app"],["app"]]
上面那一行字符串表示要执行的操作,其中“Trie”表示构造前缀树,“insert”插入,“search”搜索,“startwith”查找前缀,下面对应的是对应的字符串/前缀参数。
下面我们针对这个例子,解释一下前缀树的实现。
前文提到,前缀树是一个26叉树,所以就是有26个next指针,当然也包括一个整型val变量,一会解释这个val的意义。
所以基本的一个树节点应该是下面这个样子,构造函数应该将val初始化为0。val的含义表示从根节点到当前节点的路径构成的字符串被插入到Trie中的次数。
class Node{
public:
int val;
Node* next[26];
Node(){
val = 0;
for(int i=0;i<26;i++)
next[i] = NULL;
}
};
前面也提到,一个Trie是通过不断insert新的字符串不断变大的。那么我们关注insert操作如何实现,便知道Trie的构造。以上面的例子为例,在构造完一个根节点后,我们要插入“apple”这个单词。我们要循着“a->p->p->l->e这个路径去逐个确认每一个前缀是否已经存在,如果不存在,就建立出这个前缀的最后一个根节点,如果存在,就继续确认下一个字母”。
而当字符串的每一个前缀都确认(查找或建立)完成后,我们需要将这个字符串路径的最后一个节点的val值增1,这样表示这个字符串插入到Trie中1次,如果又插入一遍,那么这个节点的val就变成2,以此类推。
所以大致的代码结构是下面所示:
void insert(string word){
Node* p = root; //定义一个指针p,用来搜索这棵树
for(int i=0;i<word.size();i++){
//word[i]是当前前缀的最后一个字母,而word[i]-'a'就是在Node类里next数组的下标
if(p->next[word[i]-'a']==NULL){
//定义出这个前缀
p->next[word[i]-'a'] = new Node();
}
p = p->next[word[i]-'a']; //搜索下一个子串的指针方向
}
//for循环执行完,表示这个字符串已经插入完成
p->val++; //将这个字符串的尾巴节点val+1
}
我们再给一个插入的例子,来看上面的代码,假设我们插入“app”。此时的Trie中已经有了“apple这个单词。”
search和startwith函数的代码不给出,请读者自己仿照insert思考,注意利用val存储的值以及树的结构。
三、C++实现
class Node{
public:
Node* next[26];
int num;
Node(){
num = 0;
for(int i=0;i<26;i++)
next[i] = NULL;
}
};
class Trie {
public:
/** Initialize your data structure here. */
Node* root;
Trie() {
root = new Node();
}
/** Inserts a word into the trie. */
void insert(string word) {
//cout<<"inserting "<<word<<endl;
Node* p = root;
for(int i=0;i<word.size();i++){
if(p->next[word[i]-'a']==NULL){
p->next[word[i]-'a'] = new Node();
}
p = p->next[word[i]-'a'];
}
p->num++;
}
/** Returns if the word is in the trie. */
bool search(string word) {
//cout<<"searching "<<word<<" ";
Node* p = root;
for(int i=0;i<word.size();i++){
if(p->next[word[i]-'a']==NULL){
//cout<<"false"<<endl;
return false;
}
p = p->next[word[i]-'a'];
}
if(p->num!=0){
//cout<<"true"<<endl;
return true;
}
else{
//cout<<"false"<<endl;
return false;
}
}
/** Returns if there is any word in the trie that starts with the given prefix. */
bool startsWith(string prefix) {
//cout<<"start with "<<prefix<<endl;
Node* p = root;
for(int i=0;i<prefix.size();i++){
if(p->next[prefix[i]-'a']==NULL) return false;
p = p->next[prefix[i]-'a'];
}
return true;
}
};