字典树

字典树,也称前缀树,是一种哈希树的变种。典型应用是统计和排序大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:最大限度地减少无谓的字符串比较,查询效率比哈希表高。主要思想是用时间来换空间。

这里写图片描述

 

上面这棵Trie树包含的字符串集合是{in, inn, int, tea, ten, to}。每个节点的编号是我们为了描述方便加上去的。树中的每一条边上都标识有一个字符。这些字符可以是任意一个字符集中的字符。比如对于都是小写字母的字符串,字符集就是’a’-‘z’;对于都是数字的字符串,字符集就是’0’-‘9’;对于二进制字符串,字符集就是0和1。

比如上图中3号节点对应的路径0123上的字符串是inn,8号节点对应的路径0568上的字符串是ten。终结点与集合中的字符串是一一对应的。
图片文字来源:https://blog.csdn.net/weixin_39778570/article/details/81990417

3个基本性质

   1.根节点不包含字符,每条边代表一个字符。

   2.从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串。

   3.每个节点的所有子节点包含的字符都不相同。

字典树的应用:

1.字符串检索,词频统计,搜索引擎的热门查询

2. 字符串最长公共前缀

给出N 个小写英文字母串,以及Q 个询问,即询问某两个串的最长公共前缀的长度是多少.  解决方案:

  首先对所有的串建立其对应的字母树。此时发现,对于两个串的最长公共前缀的长度即它们所在结点的公共祖先个数,于是,问题就转化为了离线(Offline)的最近公共祖先(Least Common Ancestor,简称LCA)问题。

    而最近公共祖先问题同样是一个经典问题,可以用下面几种方法:

      1. 利用并查集(Disjoint Set),可以采用经典的Tarjan 算法;

      2. 求出字母树的欧拉序列(Euler Sequence )后,就可以转为经典的最小值查询(Range Minimum Query,简称RMQ)问题了;

3.  排序

Trie树是一棵多叉树,只要先序遍历整棵树,输出相应的字符串便是按字典序排序的结果。

4. 作为其他数据结构和算法的辅助结构

如后缀树,AC自动机等。

//来源https://blog.csdn.net/sunhuaqiang1/article/details/52463257


树的构建

首先我们要明白建树常用的数组建树和链表建树,这里因为我们第二维最多就26个字母,所有我们用数组建树,内存最大是所有字符串个数*26.

建树,也就是insert操作,每次插入和询问都是从跟节点开始,我们姑且把根且点当作树的tree[0][]位置,每次都从根节点开始,假设我们第一次先插入in,那个先找i是否从根有连接,没有我们就加i,层数++,再从第一层开始,没有n,再新加n;然后我们插入inn,从根节点找i,找到再从i这个位置找n,再从n这个位置找n,找不到,那么在n的下面一层加个n;

void insert(string s){
	int len = s.length();
	int pos = 0;
	for(int i=0;i<len;i++){
		int c = s[i]-'a';
		if(tree[pos][c]==0) tree[pos][c]=++tot; //tot是总数,也是我这个点所在的编号
		pos = tree[pos][c];
	}
	end[pos]=true;//这里是这个s字符串的最后一位所在的位置是有,方便查询整个字串是否在集合中
}

树的查询

search操作,和建树类似,也是从根节点开始找,如果有一个位置没找到,直接退出,找到最后一个位置并且最后一个位置在这集合中是存在的,那么我们所找的串就是在这个集合中

bool search(string s){  //查询集合中是否有整个字符串 
	int len = s.length();
	int pos=0;
	for(int i=0;i<len;i++){
		int c =s[i]-'a';
		pos = tree[pos][c];
		if(pos==0) return false;	
	}
	return end[pos];
}
/*bool searchqz(string s){ 只查询前缀 
	int len = s.length();
	int pos=0;
	int j;
	for(j=0;j<len;j++){ 
		int c=s[j]-'a';
		pos = tree[pos][c];
		if(pos==0) return false;
	}
	if(j==len) return true; //前缀所有都找完,并且都能找到
}*/

上波总代码

#include<bits/stdc++.h>
using namespace std;
const int maxn = 100100;
int tree[maxn][30];
bool end[maxn];
int tot=1;
void insert(string s){
	int len = s.length();
	int pos = 0;
	for(int i=0;i<len;i++){
		int c = s[i]-'a';
		if(tree[pos][c]==0) tree[pos][c]=++tot;
		pos = tree[pos][c];
	}
	end[pos]=true;
}
bool search(string s){  //查询集合中是否有整个字符串 
	int len = s.length();
	int pos=0;
	for(int i=0;i<len;i++){
		int c =s[i]-'a';
		pos = tree[pos][c];
		if(pos==0) return false;	
	}
	return end[pos];
}
/*bool searchqz(string s){ 只查询前缀 
	int len = s.length();
	int pos=0;
	int j;
	for(j=0;j<len;j++){
		int c=s[j]-'a';
		pos = tree[pos][c];
		if(pos==0) return false;
	}
	if(j==len) return true;
}*/
int main(){
	int t,q;//t个字符串,q次询问
	cin>>t>>q;
	string s;
	while(t--){
		cin>>s;
		insert(s);
	} 
	while(q--){
		cin>>s;
		if(search(s)) printf("有\n");
		else printf("无\n");
	}
	return 0;
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值