字典树真的好可爱啊~
字典树
又称单词查找树,Trie树,是一种树形结构,哈希树的变种。典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),经常被搜索引擎系统用于文本词频统计。它的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高。
性质:
1、根节点不包含字符,每条边代表一个字符
2、从根节点刀模一个节点,路径上经过的字符连接起来,为该节点对应的字符串
3、每个节点的所有子节点包含的字符都不相同
实现方法
以保存由26个小写字母组成的字符串为例,先设出一个根节点(作为虚点),每个节点引申出26条分支,分别代表'a' 'b' 'c' 'd' ……'z',一直顺着走下去,最后到字符串结尾时,把最后那个节点的 val 值置为true,表示以上路径代表了这个字符串。
例题应用
1、求前缀单词数量
题目链接:点我点我
题目描述:给你一堆单词,然后开始查询字符串s是多少个单词的前缀。
题目思路:
字典树裸题。因为我们要判断字符串是不是单词前缀,所以可能这个字符串的长度只能到单词的中间某个部分,我们之前标记的 val 是在单词结束的地方的,改动一下在单词的每一个字符结束时都把这个位置的 val 置为true就好了。
代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#define maxn 500000
using namespace std;
int trie[maxn][26];
int tot;
int val[maxn];
char a[maxn];
void inserts(char a[])
{
int root=0;
int len=strlen(a);
for(int i=0;i<len;i++)
{
int id=a[i]-'a';
if(!trie[root][id])
{
trie[root][id]=++tot;//tot的意义:承载字符的节点数++,如果简单置1的话后面可能会重复
}
val[trie[root][id]]++;//val[]既可以选择++,也可以简单置1,只是起标记作用,也可以声明为bool类型
root=trie[root][id];
}
}
int searchs(char b[])
{
int root=0;
int ll=strlen(b);
for(int i=0;i<ll;i++)
{
int ii=b[i]-'a';
if(!trie[root][ii])//这一位不匹配了
{
return 0;
}
root=trie[root][ii];
}
return val[root];
}
int main(void)
{
memset(trie,0,sizeof(trie));
memset(val,0,sizeof(val));
tot=0;
while(gets(a)&&a[0]!='\0')
{
//if(a[0]=='\0')
//break;
inserts(a);
}
while(~scanf("%s",a))
{
printf("%d\n",searchs(a));
}
return 0;
}
2、求最大异或值(01字典树)
01字典树:
和普通字典树其实没有什么区别,唯一不同的就是,它是把十进制数分解成二进制的01串,然后按位依次插入,也就是说,这棵字典树的每个节点都只有两条分支,分别代表0和1。
应用:01字典树适用于进行异或操作,尤其是求最大异或值问题。
比如给你数字a,b,c,然后给出数字d,问你在a,b,c组成的集合里,谁跟d的异或值最大。
这时候我们可以把a,b,c以二进制形式插入字典树里。对于数字d,我们也分解成二进制形式,然后从高位到低位在字典树里寻找与d的当前位异或值为1的数字,(因为我们既然想获得最大的异或值,肯定想让这两个数的高位异或完尽可能都是1);
现在问题转化为:如何简单而有效的判断异或完是否为1呢?这里有一个异或运算的反身性,
即假设a^b=c,那么c^b=a且c^a=b;根据这个性质,如果(当前位)idx^1的这个数肯定与idx异或后值为1;
那么就很简单了,我们采用从最高位向下贪心查找的方法,从字典树的根节点开始向下找,如果存在idx^1的节点,我们就进入这个节点,否则进入idx节点。
例题:
具体还是在代码中体现:
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#define maxn 100005
using namespace std;
typedef long long ll;
ll a[maxn];
int trie[maxn][2];//节点编号 节点表示的值
int val[maxn];
int tot;
void inserts(ll x)
{
int root=0;
for(int i=32;i>=0;i--)
{
int idx=(x>>i)&1;//把x从高位到低位取出
if(!trie[root][idx])
{
trie[root][idx]=++tot;
}
val[trie[root][idx]]=x;
root=trie[root][idx];
}
}
ll searchs(ll x)//x-要询问的这个数
{
int root=0;
for(int i=32;i>=0;i--)
{
int idx=(x>>i)&1;//依次取出x的最高位
if(trie[root][idx^1])
root=trie[root][idx^1];
else
root=trie[root][idx];
}
return val[root];
}
int main(void)
{
int n;
while(~scanf("%d",&n))
{
tot=0;
ll maxx=-1;
memset(trie,0,sizeof(trie));
for(int i=0;i<n;i++)
{
scanf("%lld",&a[i]);
inserts(a[i]);
}
for(int i=0;i<n;i++)
{
ll mid=a[i]^searchs(a[i]);
maxx=max(maxx,mid);
}
printf("%lld\n",maxx);
}
return 0;
}
呼呼