字典树又称单词查找树,其利用了字符串的公共前缀来减少查询时间,查询效率比较高。
然后如下图建树(有点丑,莫介意)。
每个父亲节点都有26个(假设只需要查询a-z)儿子,从a-z,树建好了。
然后我们定义一个数组tree[i][j],数组的i为第i个节点,j为第j个儿子.
第几个节点怎么看呢?比如上图我们要插入字符串ab,ac,da.遵循先到先得的规则,假设根节点为第0层,那么插入ab,第1层的a为第一个节点,第2层的b为第二个节点,第2层的c为第三个节点,第1层的d为第四个节点,第2层的a为第五个节点。
我们要查的是字母,怎么转换呢?我们把字符编号id=str[i]-'a',那么a-z编号就是0-26,第一个儿子就是a,第二个儿子是b....第26个儿子是z。
int cnt=0;
void add(char a[])
{
int len=strlen(a),id,root=0;
for(int i=0;i<len;i++)
{
if(!tree[root][id])
tree[root][id]=++cnt;
id=tree[root][id];
}
flag[root]=1;//当插入完一个字符串让flag[root]=1代表到root节点为一个字符串
}
查询字符串,原理同插入一样。
bool find(char s[])
{
int id,root=0,len=strlen(s);
for(int i=0;i<len;i++)
{
id=s[i]-'a';
if(!tree[root][id])//(第一次)顺着根节点的第id个儿子如果为0那肯定不在之前加入的字符串中
return false;
root=tree[root][id];//如果不为0那么顺着这个节点接着找
}
if(!flag[root])//flag[最后的节点]代表为一个字符串,为0那肯定不在之前加入字符串中
return false;
else
return true;
}
例题:HDU1251 统计难题
Problem Description
Ignatius最近遇到一个难题,老师交给他很多单词(只有小写字母组成,不会有重复的单词出现),现在老师要他统计出以某个字符串为前缀的单词数量(单词本身也是自己的前缀).
Input
输入数据的第一部分是一张单词表,每行一个单词,单词的长度不超过10,它们代表的是老师交给Ignatius统计的单词,一个空行代表单词表的结束.第二部分是一连串的提问,每行一个提问,每个提问都是一个字符串.
注意:本题只有一组测试数据,处理到文件结束.
Output
对于每个提问,给出以该字符串为前缀的单词的数量.
Sample Input
banana
band
bee
absolute
acm
ba
b
band
abc
Sample Output
2
3
1
0
#include <iostream>
#include <cstdio>
#include <string>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <map>
#include <set>
#include <vector>
#include <queue>
using namespace std;
const int maxn=1e6+100;
int tree[maxn][30],cnt;
int flag[maxn];
void add(char s[])
{
int len=strlen(s),root=0,id;
for(int i=0;i<len;i++)
{
id=s[i]-'a';
if(tree[root][id]==0)
tree[root][id]=++cnt;
root=tree[root][id];
flag[root]++;//root节点访问一次就++
}
}
int found(char b[])
{
int root=0,id,len=strlen(b);
for(int i=0;i<len;i++)
{
id=b[i]-'a';
if(tree[root][id]==0)
return 0;
root=tree[root][id];
}
return flag[root];//找到最终节点后返回它被访问的总次数
}
int main()
{
char a[maxn];
while(gets(a)&&a[0]!=NULL)
{
add(a);
}
while(gets(a))
{
cout<<found(a)<<endl;
}
return 0;
}
HDU2072 统计单词数
#include <iostream>
#include <string>
#include <sstream>
#include<cstring>
using namespace std;
const int maxn=1e6+100;
int ans;
int tree[maxn][30],tot;
int flag[maxn];
void add(string s)
{
int root=0,id,len=s.length();
for(int i=0;i<len;i++)
{
id=s[i]-'a';
if(!tree[root][id])
tree[root][id]=++tot;
root=tree[root][id];
}
if(!flag[root])
{
flag[root]=1;
ans++;
}
}
int main()
{
string str;
while(getline(cin,str)&&str[0]!='#')
{
memset(flag,0,sizeof(flag));
ans=0;
stringstream ss(str);
string s;
while(ss>>s)
{
add(s);
}
cout<<ans<<endl;
}
return 0;
}
HDU 1247 Hat’s Words
#include <iostream>
#include <string>
#include <sstream>
#include<cstring>
#include<cstdio>
using namespace std;
const int maxn=1e6+100;
int ans;
int tree[maxn][100],tot;
bool flag[maxn];
char str[maxn][20];
void add(char s[])
{
int root=0,id,len=strlen(s);
for(int i=0;i<len;i++)
{
id=s[i]-'a';
if(!tree[root][id])
tree[root][id]=++tot;
root=tree[root][id];
}
flag[root]=true;
}
int found(char s[])
{
int root=0,id,len=strlen(s);
for(int i=0;i<len;i++)
{
id=s[i]-'a';
if(!tree[root][id])
return 0;
root=tree[root][id];
}
if(!flag[root])
return 0;
else
return 1;
}
int main()
{
int k=-1;
while(cin>>str[++k])
{
add(str[k]);
}
for(int i=0;i<k;i++)//一共k个串
{
char s1[50],s2[50];
int len=strlen(str[i]);
for(int j=0;j<len;j++)//第j个串的长度
{
memset(s1,'\0',sizeof(s1));//定义两个数组,一个存这个串的前面,一个存后面
memset(s2,'\0',sizeof(s2));
strncpy(s1,str[i],j);//前半部分0-j
strncpy(s2,str[i]+j,len-j);//后半部分j-len
if(found(s1)&&found(s2))//如果这个串拆分开以后的两个串能够找到,则输出
{
cout<<s1<<s2<<endl;
break;
}
}
}
return 0;
}
POJ 2001 Shortest Prefixes
#include <cstdio>
#include <algorithm>
#include <cstring>
#include<iostream>
using namespace std;
const int maxn=1e6+100;
char str[maxn][26];
int tree[maxn][26];
int tot;
int sum[maxn][26];
void add(char s[])
{
int len=strlen(s),root=0,id;
for(int i=0;i<len;i++)
{
id=s[i]-'a';
if(!tree[root][id])
tree[root][id]=++tot;
sum[root][id]++;
root=tree[root][id];
}
}
void found(char s[])
{
int len=strlen(s),root=0,id;
for(int i=0;i<len;i++)
{
id=s[i]-'a';
if(sum[root][id]==1)
{
printf("%c",s[i]);
break;
}
printf("%c",s[i]);
root=tree[root][id];
}
}
int main()
{
int k=0;
while(scanf("%s",str[k])!=EOF)
{
add(str[k++]);
}
for(int i=0;i<k;i++)
{
printf("%s ",str[i]);
found(str[i]);
printf("\n");
}
return 0;
}
POJ 3630 Phone List(这道题是找是输入的字符串中是否有一个字符串是另一个字符串的前缀,那么可以在字典树建立的过程中判断,如果该树的root 和id被标记过,即不为0,而且已经进行到最后一个字符判断,那么肯定是某字符串前缀,以及包括字符串本身,如果都遍历完了,而且该root标记为1,那么它也是某字符串前缀,即这个串本身)。
#include <iostream>
#include <string>
#include <sstream>
#include<cstring>
#include<cstdio>
using namespace std;
const int maxn=1e5+3;
int tree[maxn][10],tot;
bool flag[maxn];
char str[maxn];
bool add(char s[])
{
int root=0,id,len=strlen(s);
bool f=false;
for(int i=0;i<len;i++)
{
id=s[i]-'0';
if(!tree[root][id])
tree[root][id]=++tot;
else if(i==len-1)//如果该树的root 和id被标记过,即不为0,而且已经进行到最后一个字符判
// 断,那么肯定是某字符串前缀
f=true;
root=tree[root][id];
if(flag[root]==true)//如果是这个串本身也是前缀
f=true;
}
flag[root]=true;
return f;
}
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
int n;
scanf("%d",&n);
int flag1=0;
memset(tree,0,sizeof(tree));
memset(flag,false,sizeof(flag));
tot=0;
for(int i=0;i<n;i++)
{
scanf("%s",str);
if(add(str)==true)
flag1=1;
}
if(flag1)
puts("NO");
else
puts("YES");
}
return 0;
}