Trie树有一些特性:
1)根节点不包含字符,除根节点外每一个节点都只包含一个字符。
2)从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串。
3)每个节点的所有子节点包含的字符都不相同。
4)如果字符的种数为n,则每个结点的出度为n,这也是空间换时间的体现,浪费了很多的空间。
5)插入查找的复杂度为O(n),n为字符串长度。
怎么说呢。Trie树其实是把单词(以单词为例)的每个字母依次连成一条链,每个单词都连在一个根节点上,如果两个单词拥有相同的一部分元素,那这两条链之间可能有部分重合。
图片是网上找的。。
以上图为例,红色节点标记单词结束。最左一条链有两个单词ab和abc,他们有部分重合。
那其实写程序的时候也是按照相同的方式建树。
我习惯是用数组。
int index[N][30];//index存储的是树上的点u经过字母t所指向的点。
int num[N];
//记录该字母在这条链上出现的次数,需要根据具体题目调整想要记录的数据
int cnt=0;
void Insert(char str[]){
int u=0;
for(int i=0;i<strlen(str);i++){
int tmp=str[i]-'a'+1;
if(index[u][tmp]==0)
index[u][tmp]=++cnt;
num[u]++;
u=index[u][tmp];
}
num[u]++;
}
查询的时候也是一样的方式
从根节点开始查起,一层一层往下走,找到你想要找的单词的末尾,然后取得相应的数据。
int Query(char str[]){
int u=0;
for(int i=0;i<strlen(str);i++){
int tmp=str[i]-'a'+1;
if(!index[u][tmp])
return 0;
u=index[u][tmp];
}
return num[u];
}
然后看下具体题目
http://acm.hdu.edu.cn/showproblem.php?pid=1251
这是一道根据给定前缀求单词数量的问题。就是很裸的trie树。
建树:将单词表中每个单词插进去,每次记录一个每个节点的数目(表明有多少个单词经过该点)
然后直接根据前缀查询。
#include <bits/stdc++.h>
#define N 1000010
using namespace std;
int index[N][30];
int num[N];
int cnt=0;
void Insert(char str[]){
int u=0;
for(int i=0;i<strlen(str);i++){
int tmp=str[i]-'a'+1;
if(index[u][tmp]==0)
index[u][tmp]=++cnt;
num[u]++;
u=index[u][tmp];
}
num[u]++;
}
int Query(char str[]){
int u=0;
for(int i=0;i<strlen(str);i++){
int tmp=str[i]-'a'+1;
if(!index[u][tmp])
return 0;
u=index[u][tmp];
}
return num[u];
}
int main(){
char str[15];
while(gets(str)){
if(strlen(str)==0)
break;
Insert(str);
}
while(cin>>str){
int cnt=Query(str);
cout<<cnt<<endl;
}
return 0;
}
上述题目其实最暴力的想法是把每个前缀跟每个单词进行比对,这样效率是O(m*n*len(m)),其中n是单词个数,m是前缀个数,len(m)是前缀长度。
然而使用trie树建树是O(n*len(n)),查询是 O(m*len(m))。相比较,效率会高很多。
http://acm.hdu.edu.cn/showproblem.php?pid=1075
这道题是一道替换单词的题,可以用map写,但据说trie树的效率更高。没写map,因为想找trie练手。
刚开始看到的时候,虽然知道知识点是trie,但是相较于前一题,这该怎么存替换的单词啊。然后发现我们可以在每个单词的末尾存储对应的替换单词,其余节点则不用管。查询的时候直接返回所查询的单词末尾存储的数据。
我的代码是PE,还没找到哪里有问题。。
#include <bits/stdc++.h>
#define N 1000010
using namespace std;
int index[N][30];
string word[N];
int cnt=0;
void Insert(string str1,string str2){
int u=0;
for(int i=0;i<str2.size();i++){
int tmp=str2[i]-'a';
if(!index[u][tmp])
index[u][tmp]=++cnt;
u=index[u][tmp];
}
word[u]=str1;
}
string Query(string str){
int u=0;
string s="";
for(int i=0;i<str.size();i++){
int tmp=str[i]-'a';
if(!index[u][tmp])
return s;
u=index[u][tmp];
}
return word[u];
}
int main(){
string str1,str2;
cin>>str1;
while(cin>>str1>>str2){
if(str1=="END")
break;
Insert(str1,str2);
}
while(getline(cin,str1)){
if(str1=="END")
break;
int l=0,r=0,i=0,pre=0;
while(i<str1.size()){
while(i<str1.size()&&(!isalpha(str1[i])))
i++;
l=i;
for(int j=pre;j<l;j++)
cout<<str1[j];
if(i>=str1.size())
break;
while(i<str1.size()&&isalpha(str1[i]))
i++;
r=i;
if(i>=str1.size())
break;
string s=str1.substr(l,r-l);
string tp=Query(s);
if(tp.size())
cout<<tp;
else{
for(int j=l;j<r;j++)
cout<<str1[j];
}
if(i>=str1.size())
break;
pre=r;
}
cout<<endl;
}
return 0;
}
其实感觉基础的trie树还是很好理解的。就根据脑子里面的图建一棵树出来,然后查询的时候从上向下走相应的链就行了。唯一不好想的可能就是数据存储,以及怎么想到某道题是trie树。