【数据结构】字典树(TrieTree)细解

问题引入

给你n串字符串,求有多少字符串是其它字符串的前缀。
暴力搜索固然可以,但当n很大时就会超时,于是我们采用字典树的方法做。

介绍

字典树,又称单词查找树,Trie树,是一种树形结构,是一种哈希树的变种。
它的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高。

原理

想一想,如果给你一个单词"homo",在字典里查,你会如何查呢?
首先,你会先找到字母"h"分区,再在里面找到"o"分区,接着找"m"和"o",最终查到单词"homo"。
同理,字典树其实就是构建了一棵写着字符的树,使你查单词时就像在查字典一样。

例:有五个单词bb aa aaa aabb aaba
我们对其构建字典树。
在这里插入图片描述
其中红色表示有一个单词以此节点结尾。
于是,查单词aabb时,发现:a有,->a有,->b有,->b有,所以有单词aabb。
而查单词ab时会发现:a有,->b没有,所以没有单词ab。

代码实现

转字符

对于输入的东西,我们将它转换为字符串再处理。
e.g.

int tem=d[i]-'0';

构建树

对于一个字符串,例如ab。
我们发现a有了,于是不管它,第二次发现b没有,于是扩展出节点b。
同时,我们用idx记录子节点编号,再用数组记录红色节点。
e.g.

inline void insert(string d)
{
	int p=0;
	for(int i=0;i<d.size();i++)
	{
		int tem=d[i]-'0';
		if(!f[p][tem])
			f[p][tem]=++idx;
		p=f[p][tem];
	}
	cnt[p]++;
}

解释:
f[i][j]表示第i号节点的子节点为j的子节点编号
idx表示这个子节点的编号
p表示当前在第几编号
cnt[i]表示有几个单词以编号i的子节点结尾。
显然,如果还没有这个子节点,就要创建它:

if(!f[p][tem])
	f[p][tem]=++idx;

最后,用数组累记它:

cnt[p]++;

查找

对于一个单词,找它的第一位,如果有就继续,没有则退出。
e.g.

inline bool find(string d)
{
	int p=0;
	for(int i=0;i<d.size();i++)
	{
		int tem=d[i]-'0';
		if(f[p][tem])
			p=f[p][tem];
		else
			return false;
	}
	return true;
}

显然,如果连第i位都没找到,肯定没这个单词:

if(!f[p][tem])
	return false;

反之,就继续:

if(f[p][tem])
	p=f[p][tem];

例题

题目

小明是牛老大,某一天要带领奶牛们越狱.为了越狱方便,奶牛们可以互相发送秘密信息.
信息是二进制的,共有 M(1 <= M <= 50000)条,反间谍能力很强的约翰已经部分拦截了这些信息,知道了第 i 条二进制信息的前 b(i)(1 <= b(i) <=10000)位,他同时知道,奶牛使用 N(1 <= N <= 50000)条暗号.但是,他仅仅知道第 j 条暗号的前 c(j)(1 <= c(j) <= 10000)位。
对于每条暗号 j,他想知道有多少截得的信息能够和它匹配。也就是说,有多少信息和这条暗号有着相同的前缀。当然,这个前缀长度必须等于暗号和那条信息长度的较小者。
在输入文件中,位的总数(b(i)和c(i))不会超过 500000。

输入格式
第一行两个整数M和N
第二行到 M+1行:第 i+1行描述截获消息b(i)位,后跟 b(i)个空格分隔 0 和 1。
接下来是N行,每行第一个数是c(j),后跟c(j)个空格分割的0和1。

输出格式
共N行,每行输出一个数表示共有几条秘密消息与该暗号匹配

输入/输出例子1

输入:

4 5
3 0 1 0
1 1
3 1 0 0
3 1 1 0
1 0
1 1
2 0 1
5 0 1 0 0 1
2 1 1

输出:

1
3
1
1
2

样例解释

Code

#include<bits/stdc++.h>
using namespace std;
int n,m,x,y;
int cnt2[1000001],cnt1[1000001],f[100001][3],idx,f2[100001];
string s[50001],s1;
map<string,int>a;
inline void insert(string d)//建树
{
	int p=0;
	for(int i=0;i<d.size();i++)
	{
		int tem=d[i]-'0';//转字符
		if(!f[p][tem])
			f[p][tem]=++idx;
		p=f[p][tem];
		cnt2[p]++;
	}
	cnt1[p]++;
}
inline int find(string d,int z)//查找
{
	int p=0,ans=0;
	for(int i=0;i<d.size();i++)
	{
		int tem=d[i]-'0';
		if(!f[p][tem])
			return ans;
		p=f[p][tem];
		ans+=(cnt1[p]);
	}
	return ans+cnt2[p]-cnt1[p];
}
signed main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&x);
		s[i]="";
		for(int j=1;j<=x;j++)
			scanf("%d",&y),s[i]+=(y+'0');
		insert(s[i]);
	}
	for(int i=1;i<=m;i++)
	{
		scanf("%d",&x);
		s1="";
		for(int j=1;j<=x;j++)
			scanf("%d",&y),s1+=(y+'0');
		printf("%d\n",find(s1,i));
	}
}

总结

其实字典树就是利用了公共前缀的一个树形结构,它可以用来处理许多事情,是很好的一个算法。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值