Tire树
即字典树,又称单词查找树,利用字符串的公共前缀,省去很多重复的比较,从而节省查询时间。
经常被搜索引擎系统用于文本词频统计。
上图便是一颗字典树,
可以看到,该树有以下特点:
树的根节点不含字符,其余节点含且只含一个字符
兄弟间所含字符各不相同
红色表示在该点形成一个单词(从根节点到该节点路径上所有字符连成的字符串)
例如,abc、abcd、abd等都是单词,上图共有7个红色节点,故有7个单词。
ab并不是单词,只是某些单词的前缀。
假设我们要查询abc这个单词,首先要查询根节点的子节点中含不含a字符,如果不包含,则说明不包含abc这个单词。
如果根节点的子节点中包含a字符,那么我们只需从a节点开始查找,不必考虑第二层的b、e、h结点,如果a节点的子节点中不含b字符,则说明不包含abc这个单词,如果含b字符,则继续判断b字符的子节点中有没有c字符…
如果到了单词的最后一个字母,还需判断该节点“是否为红色”,如果是红色,则该字典树中存在这个单词。
可以看到,查询一个长度为n的单词,时间复杂度仅仅为O(n)。
至于字典树的构建,与查询的算法有些相仿。
例如,要在一颗字典树中插入单词efh,先看根节点的子节点中有没有e,如果没有,则为根节点创建一个子节点e,然后继续为e节点创建一个子节点f,然后……
如果根节点的子节点中有e,则继续看e节点的子节点中有没有f,无则创建,有则继续查询……
最后,插入或查询到最后一个字母,需把该节点“标志为红色”,表明在该节点形成一个单词。
#include<stdio.h>
struct trieTree{
bool isstr;
//isstr为true表明该节点处形成单词,也就是上文的“红色”
trieTree* next[26];
//每个节点有26个子节点,不过还未赋“字符”
};
trieTree* _init(){
//节点初始化
trieTree* r = new trieTree;//申请空间
r->isstr = false;//该节点不形成单词
for(int i=0;i<26;i++)
r->next[i] = NULL;//为每个子节点赋值为空
return r;
}
void _insert(char* str,trieTree* root){
//插入单词,str是待插入单词的首字母的地址
trieTree* tmp = root;
while(*str!='\0'){//字符串的结束字符是'\0'
if(!tmp->next[*str-'a']){
//next[i]中,i范围是0到25,分别代表字符a到z,
//如果*str=b,那么next[*str-'a'],
//即next[1],表示tmp节点的b子节点
//!tmp->next[*str-'a'] 指:tmp不存在b子节点
trieTree* r = _init();//创建结点
tmp->next[*str-'a'] = r;
tmp = r;
}
else
tmp = tmp->next[*str-'a'];
//如果tmp存在(*str)节点,使tmp指向此节点
str++;//str指向下一个字母的地址
}
tmp->isstr = true;//“标记为红色”
}
bool _search(char* str,trieTree* root){
//查询单词,返回true表示待查询单词存在于字典树中,返回false表示不存在
trieTree* tmp = root;
while(*str!='\0'){
if(!tmp->next[*str-'a']){
return false;
//如果找不到(*str),说明字典树中不存在此单词,返回false
}
else
tmp = tmp->next[*str-'a'];
str++;
}
return tmp->isstr;
//待查询单词的所有字母都找到了,还需判断最后一个单词结点是否为“红色”
}
void _del(trieTree* root){
//释放内存
for(int i=0;i<26;i++)
if(root->next[i])
_del(root->next[i]);//递归
delete root;
}
int main(){
trieTree* root = _init();
char str[4][10] = {"hello","world","software","engineer"};
for(int i=0;i<4;i++)
_insert(str[i],root);//插入单词
char str1[10];
while(scanf("%s",str1),str1[0]!='#'){//输入'#'开头字符串结束输入
int ans = _search(str1,root);
puts((ans?"yes":"no"));//如果ans==1输出yes,否则输出no
}
_del(root);
return 0;
}
当然,字典树的功能不止于查询某单词是否存在,经过适当修改,可以查询“以某字符串为前缀的单词个数”、“一组单词中某单词出现的次数”等。