字典树(Trie)
内容来自:www.cnblogs.com
字典树,顾名思义,是关于“字典”的一棵树。即:它是对于字典的一种存储方式(所以是一种数据结构而不是算法)。这个词典中的每个“单词”就是从根节点出发一直到某一个目标节点的路径,路径中每条边的字母连起来就是一个单词。
上图理解:
字典树的功能
根据字典树的概念,我们可以发现:字典树的本质是把很多字符串拆成单个字符的形式,以树的方式存储起来。所以我们说字典树维护的是”字典“。那么根据这个最基本的性质,我们可以由此延伸出字典树的很多妙用。简单总结起来大体如下:
1、维护字符串集合(即字典)。
2、向字符串集合中插入字符串(即建树)。
3、查询字符串集合中是否有某个字符串(即查询)。
4、统计字符串在集合中出现的个数(即统计)。
5、将字符串集合按字典序排序(即字典序排序)。
6、求集合内两个字符串的LCP(Longest Common Prefix,最长公共前缀)(即求最长公共前缀)。
我们可以发现,以上列举出的功能都是建立在“字符串集合”的基础上的。再一次强调,字典树是“字典”的树,一切功能都是“字典”的功能。这也为我们使用字典树的时候提供了一个准则:看到一大堆字符串同时出现,就往哈希和Trie树那边想一下。
插入操作
假如这个字典只包括26个英文字母(暂且都定为小写),那么这个树的深度会由具体单词不一样而定。但是它的广度范围是可以提前确定好的。对于每个节点,广度最大为26。(因为每个节点的下一个字母(即后缀点)只可能是26个字母。)那么我们可以用结构体开好这个“虚拟全树“。然后通过深度迭代向里面尝试加入单词。
我们开一个包含26个后缀指针的结构体。用变量now来表示指向当前节点编号的一个指针,用tot变量表示点的编号。end数组表示当前单词的“目标节点”即单词结尾的那个节点具体是哪个单词的词尾。
那么代码就长成这样:
struct node
{
int next[27];
}trie[100];
int tot=1;
int insert(char s[],int id)
{
int now=0;
int len=strlen(s);
for(int i=0;i<len;i++)
{
int ch=s[i]-'a'+1;
if(!trie[now].next[ch])
{
trie[now].next[ch]=tot;
tot++;
}
now=trie[now].next[ch];
}
end[now]=id;
}
查询操作
查询操作和刚刚的思路大同小异,因为我们已经有了一个“虚拟全树”,那么我们还是按深度向下迭代,对于需要查询的字符串的当前字符,如果这个对应的字符指针为空,就说明不含这个单词,直接跳出即可。当我们都迭代完成之后,直接返回end[now]即可。(注意,这里不能直接返回1或true,假如字典中只保存了一个字符串abcdef,而我们查询的是abc,它可以不被跳出地一直迭代到最后,但是它并不是字典中的单词。即,需要考虑字典中单词子串的情况)。
bool search(char s[])
{
int len=strlen(s);
int now=0;
for(int i=0;i<len;i++)
{
int ch=s[i]-'a'+1;
if(!trie[now].next[ch])
return 0;
}
return end[now];
}
P2580 于是他错误的点名开始了
题目背景
XS中学化学竞赛组教练是一个酷爱炉石的人。
他会一边搓炉石一边点名以至于有一天他连续点到了某个同学两次,然后正好被路过的校长发现了然后就是一顿欧拉欧拉欧拉(详情请见已结束比赛 CON900)。
题目描述
这之后校长任命你为特派探员,每天记录他的点名。校长会提供化学竞赛学生的人数和名单,而你需要告诉校长他有没有点错名。(为什么不直接不让他玩炉石。)
输入格式
第一行一个整数 n,表示班上人数。
接下来 n 行,每行一个字符串表示其名字(互不相同,且只含小写字母,长度不超过 50)。
第 n+2 行一个整数 m,表示教练报的名字个数。
接下来 m 行,每行一个字符串表示教练报的名字(只含小写字母,且长度不超过 50)。
输出格式
对于每个教练报的名字,输出一行。
如果该名字正确且是第一次出现,输出 OK,如果该名字错误,输出 WRONG,如果该名字正确但不是第一次出现,输出 REPEAT。
输入输出样例
输入 #1
5
a
b
c
ad
acd
3
a
a
e
输出 #1
OK
REPEAT
WRONG
说明/提示
对于40% 的数据,n≤1000,m≤2000。
对于70% 的数据,n≤10^4,m≤2×10^4。
对于100% 的数据,n≤10^4,m≤10^5。
#include<stdio.h>
#include<string.h>
int tot=1;
struct note
{
int next[27];
int flag;
}trie[1000010];
int insert(char s[])
{
int now=0;//字典树的编号
int len=strlen(s);
for(int i=0;i<len;i++)
{
int ch=s[i]-'a'+1;
if(!trie[now].next[ch])
{
trie[now].next[ch]=tot;
tot++;
}
now=trie[now].next[ch];
}
trie[now].flag=1;
}
void search(char s[])
{
int len=strlen(s);
int now=0;
for(int i=0;i<len;i++)
{
int ch=s[i]-'a'+1;
if(!trie[now].next[ch])
{
printf("WRONG\n");
return ;
}
now=trie[now].next[ch];
}
if(trie[now].flag==1)
{
printf("OK\n");
trie[now].flag++;
return ;
}
if(trie[now].flag==0)
{
printf("WRONG\n");
return ;
}
else
{
printf("REPEAT\n");
return ;
}
}
int main()
{
int n,m;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
char str[60];
scanf("%s",str);
insert(str);
}
scanf("%d",&m);
for(int i=1;i<=m;i++)
{
char str[60];
scanf("%s",str);
search(str);
}
}
之前我用原代码时以为end数组记录的就是每个单词的最后一个字母,所以在统计次数时加了一个map数组记录end[now]如果出现就++,但是提交的时候全错了。然后参照了别人的代码,在建造字典树的结构体中加了flag用于统计出现的次序,因为不会用指针,所以在search函数中完成判断输出。