作用
Trie是前缀树,通俗的来说就是一棵字符树,它的作用是把许多字符串整合储存,做成一个字符串集合,有时可以达到快速查找的效果(比如求多少个单词是一个句子的前缀)。
实现
比如有she,her,hers,his,him这几个单词,我们就可以建立这么一个Trie。
首先会发现Trie的一个特性:必有一个根节点(把根节点记为0)。然后每条边都有一个权值,权值即为字符(一般用取id的方法来当成权值)。然后那些红色的是什么东西?不难想到这几个节点就是单词节点,也就是存在字符串的节点。
那么记录son[i][j]表示i节点j编号的儿子(没有就给0,尽管0节点是根节点,但是并没有什么关系,因为不可能有边指向根节点),以及num[i]表示i节点的权值,就可以构造一棵Trie了。
Trie构造的时间复杂度为ΣL,L表示字符串的长度,询问时间为Trie的最大深度(然而实际处理中这两个操作的时间复杂度可能会有所降低)。Trie不仅有高效的时间复杂度,还为AC自动机等算法提供了基础。
拓展
不难发现Trie的空间复杂度为O(ΣL*id),id表示字符个数。当id范围为整个char时,开销是十分高昂的。如果这时候时间要求比较宽松,可以使用邻接表来减小空间开销(这样就保证没有的儿子不记录,则空间复杂度减少为O(ΣL)),但是时间开销上涨了(因为搜索的时候你要判断是哪个儿子)。所以,权衡空间和时间是很重要的。
模板
#include<cstdio>
const int maxn=100000,maxl=10,maxi=26; //maxi表示字符总数
struct Trie
{
int son[maxn*maxl+5][maxi],w[maxn*maxl+5],len;
int geti(char ch) {return ch-'a';} //得到字符标号,这里默认只有小写字母
void Insert(char *s) //插入s
{
int now=0; //now表示当前位置
for (int i=1;s[i];i++)
{
if (son[now][geti(s[i])]==0) son[now][geti(s[i])]=++len; //并没有出现过,加上
now=son[now][geti(s[i])]; //继续遍历
}
w[now]++; //最终位置now就是单词节点
}
int get_pre(char *s) //查找s的前缀个数
{
int now=0,num=0; //num表示数量
for (int i=1;s[i];i++)
{
num+=w[now]; //加上now节点的单词数量
if (son[now][geti(s[i])]!=0) now=son[now][geti(s[i])]; else break;
}
return num;
}
};
int n,m;
Trie tr;
char now[maxl+5];
bool Eoln(char ch) {return ch==10||ch==13||ch==EOF;}
int reads(char *s)
{
int len=0;char ch=getchar();if (ch==EOF) return EOF;
s[++len]=ch;while (!Eoln(s[len])) s[++len]=getchar();s[len--]=0;
return 0;
}
void readln() {scanf("%*[^\n]");getchar();}
int main()
{
freopen("Trie.in","r",stdin);
freopen("Trie.out","w",stdout);
scanf("%d",&n);readln();
for (int i=1;i<=n;i++) reads(now),tr.Insert(now);
scanf("%d",&m);readln();
for (int i=1;i<=m;i++) reads(now),printf("%d\n",tr.get_pre(now));
return 0;
}