学习于:
https://www.cnblogs.com/-citywall123/p/11178106.html
一.概述
字典树,又称单词查找树,Trie树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高。
二.理解
-
字典树用边表示字母
-
有相同前缀的单词公用前缀节点,那我们可以的得出每个节点最多有26个子节点(在单词只包含小写字母的情况下)
-
整棵树的根节点是空的。为什么呢?便于插入和查找,这将会在后面解释。
-
要对单词最后一个字母的vis[i]置为true,其他的都是false。true代表这个单词结束。
三.模板
A.插入单词
从图中可以直观看出,从左到右扫这个单词,如果字母在相应根节点下没有出现过,就插入这个字母;否则沿着字典树往下走,看单词的下一个字母。
设数组trie[i][j]=k,表示编号为i的节点的第j个孩子是编号为k的节点。
i,k表示节点的位置编号,这是相对整棵树而言的;
j,表示节点i的第j的孩子,这是相对节点i而言的。(ASCII)
因为每个节点最多有26个子节点,我们可以按他们的字典序从0——25编号,也就是他们的ASCLL码 - ’a’。相同字母的编号相同
代码:
void insertt(char *a)//插入单词s
{
int len=strlen(a);//单词s的长度
int root=0;//根节点编号为0
for(int i=0;i<len;i++)
{
int id=a[i]-'a';//第二种编号
if(tree[root][id]==0)//如果之前没有从root到id的前缀
{
tree[root][id]=++tot;//插入,tot为总节点数
root=tot;
}
else root=tree[root][id];//顺着字典树往下走
//num[root]++;///num[i]以第i个节点所代表的前缀为前缀的单词个数
}
//vis[root]=1;///对单词最后一个字母的vis[i]置为1,其他的都是初始值0
}
B.查找
代码
bool findd(char *s)
{
int len=strlen(s);
int root=0;//从根结点开始找
for(int i=0;s[i];i++)
{
int x=s[i]-'a';
if(tree[root][x]==0) return false;//以root为头结点的x字母不存在,返回0
root=tree[root][x];//往下走
}
return true;
}
C.初始化
void init()
{
for(int i=0;i<=tot;i++)
{
memset(tree[i],0,sizeof(tree[i]));
num[i]=vis[i]=0;
}
tot=0;
}
四.应用
The 2019 ICPC Asia Shanghai Regional
链接:https://ac.nowcoder.com/acm/contest/4370/B
代码1:
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <iostream>
#include <algorithm>
using namespace std;
int tree[100008][12],num[100008],vis[100008];
int tot=0;
void insertt(char *a)//插入单词s
{
int len=strlen(a);//单词s的长度
int root=0;//根节点编号为0
for(int i=0;i<len;i++)
{
int id=a[i]-'0';//第二种编号
if(tree[root][id]==0)//如果之前没有从root到id的前缀
{
tree[root][id]=++tot;//插入,tot为总节点数
root=tot;
}
else root=tree[root][id];//顺着字典树往下走
num[root]++;///num[i]以第i个节点所代表的前缀为前缀的单词个数
}
vis[root]=1;///对单词最后一个字母的vis[i]置为1,其他的都是初始值0
}
bool findd(char *s)
{
int len=strlen(s);
int root=0;//从根结点开始找
for(int i=0;s[i];i++)
{
int x=s[i]-'0';
if(tree[root][x]==0) return false;//以root为头结点的x字母不存在,返回0
root=tree[root][x];//往下走
}
return true;
}
void init()
{
for(int i=0;i<=tot;i++)
{
memset(tree[i],0,sizeof(tree[i]));
num[i]=vis[i]=0;
}
tot=0;
}
int main()
{
char f[15];
int T,n;
cin >> T;
for(int k = 1; k <= T; k ++)
{
int flag=1;
init();
scanf("%d",&n);
for(int j=1;j<=n;j++)
{
scanf("%s",f);
insertt(f);
}
for(int i=0;i<=tot;i++)
{
if(vis[i]&&num[i]>1)///是整个单词,并且这整个单词是前缀
{
flag=0;
break;
}
}
if(flag==0)printf("Case #%d: No\n",k);
else printf("Case #%d: Yes\n",k);
}
return 0;
}
代码2:
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <iostream>
#include <algorithm>
using namespace std;
int tree[100008][12],num[100008],vis[100008];
int tot=0;
void insertt(char *a)//插入单词s
{
int len=strlen(a);//单词s的长度
int root=0;//根节点编号为0
for(int i=0;i<len;i++)
{
int id=a[i]-'0';//第二种编号
if(tree[root][id]==0)//如果之前没有从root到id的前缀
{
tree[root][id]=++tot;//插入,tot为总节点数
root=tot;
}
else root=tree[root][id];//顺着字典树往下走
num[root]++;///num[i]以第i个节点所代表的前缀为前缀的单词个数
}
vis[root]=1;///对单词最后一个字母的vis[i]置为1,其他的都是初始值0
}
int findd(char *s)
{
int root=0;//从根结点开始找
for(int i=0;s[i];i++)
{
int x=s[i]-'0';
if(tree[root][x]==0) return 0;//以root为头结点的x字母不存在,返回0
root=tree[root][x];//往下走
}
return num[root];
}
void init()
{
for(int i=0;i<=tot;i++)
{
memset(tree[i],0,sizeof(tree[i]));
num[i]=vis[i]=0;
}
tot=0;
}
char word[100008][12];
int main()
{
int T,n;
cin >> T;
for(int k = 1; k <= T; k ++)
{
memset(word, 0, sizeof(word));
int flag=1;
init();
scanf("%d",&n);
for(int i = 1; i <= n; ++i)
{
scanf("%s", &word[i]);
insertt(word[i]);
}
for(int i = 1; i <= n; ++i)
{
if(flag)
{
if(findd(word[i]) > 1)
{
flag = 0;
break;
}
}
}
if(flag==0)printf("Case #%d: No\n",k);
else printf("Case #%d: Yes\n",k);
}
return 0;
}