trie 树详解 + 例题

这篇做的笔记la~

ほら、もうすぐ晴れますよ!

字典树

字典树(trie 树)是一种用于实现字符串快速检索的多叉树结构。
trie 树的每个节点都拥有若干个字符指针,若在插入或检索字符串时扫描到一个字符 c,就沿着当前节点的 c 字符指针,走向该指针指向的节点。
下图即为一个简易版字典树,存储了单词:abc、bac、abd。

图有点奇怪请见谅

实现:

初始化:

一棵空的 \(trie\) 树只包含一个根节点,该字符的指针均指向空。

不过字符还需要转换成一个数字,这里我们就需要用到一个类似于map映射的东西

struct node{
	int cnt=0; //cnt 表示到这个节点为止,一共有多少个前缀
	int son[65]; // 每个节点的分支
}z[maxn]; 

int getnumber(char ch)//把字符转换成数字
{
	if(ch>='A'&&ch<='Z') return ch-'A';
	if(ch>='a'&&ch<='z') return ch-'a'+26;
	return ch-'0'+52;
 } 

插入:

当需要插入一个字符串 \(s\) 时,我们令一个指针 \(P\) 起初指向根节点。然后依次扫描 \(s\) 中的每一个字符 \(c\)

  • \(P\)\(c\) 字符指针指向一个已经存在的节点 \(Q\) ,则令 \(P=Q\)
  • \(P\)\(c\) 字符指针指向空,则新建一个节点 \(Q\)\(P\)\(c\) 字符指针指向 \(Q\) ,然后令 \(P=Q\)
  • \(s\) 中的字符扫描完毕,在当前节点 \(P\) 上标记它是一个字符串的末尾
void insert(string s) //插入字符串 s
{
	int now=1;//从根节点开始查找 
	for(int i=0;i<s.length();i++) //分解每一个字符
	{
		int num=getnumber(s[i]);
		if(!z[now].son[num]) //从 now 出发没有这个字符
			z[now].son[num]=++cnt;//添加这条边和这条边连向的节点 
        //z[now].cnt++;//到这里有前缀 
	 	now=z[now].son[num];//沿着这条边查找下一个字符 
	} 
	z[now].cnt++; 
	//若已经是字符串的最后一个字符,则代表字典树的这个节点是一个单词的末尾,统计的cnt需要+1
 	return ;
 } 

检索:

当需要检索一个字符串 \(s\)\(Trie\) 中是否存在时,我们令一个指针 \(P\) 起初指向根节点,然后依次扫描 \(s\) 中的每个字符 \(c\)

  • \(P\)\(c\) 字符指针指向空,则说明 \(S\) 没有被插入到 Trie 树中,结束检索;
  • \(P\)\(c\) 字符指针指向一个已经存在的节点 \(Q\) ,则令 \(P=Q\)
  • 若在当前节点 \(P\) 被标记为一个 字符串的末尾,则说明 \(S\)\(Trie\) 树中存在,否则说明 \(S\) 没有被插入过
void query(string s)
//查找字符串 s 在 trie 中是否存在
{
	int now=1; 
	for(int i=0;i<s.length();i++)
	{
		int num=getnumber(s[i]);
		if(z[now].son[num]==0)
		{
			cout<<0<<endl;//表示不存在
			return; 
		}
		now=z[now].son[num];
	}
	cout<<z[now].cnt<<endl;
	return;
 } 

例题:

P8306 【模板】字典树

这个输入我看了好久才明白 qwq

  • 题目意思:
    给你 \(n\) 个字符串 \(si\),再给你 \(q\) 次询问,每次询问给你一个字符串 \(ti\),求出 \(ti\) 上面 \(n\)\(si\) 中多少个的前缀。

一个字符串 \(t\)\(s\) 的前缀当且仅当从 \(s\) 的末尾删去若干个(可以为 0 个)连续的字符后与 \(t\) 相同。

  • 思路:
    让每个节点的 cnt 表示到达这个节点的字符串数量,每次询问查找 \(ti\) 的最后一个字符所在的节点的 cnt 值。
  • 代码:
#include<bits/stdc++.h>

using namespace std;

int T;
int n,q;
int cnt,start; 

struct node{
	int son[65];//大小写敏感
	int cnt;//表示到达这个点的字符串有多少 
}z[3000100]; 

int getnumber(char ch)
{
	if(ch>='a'&&ch<='z')return ch-'a'+1; 
    //小写字母占据 1~26 的数组范围
	else if(ch>='A'&&ch<='Z')return ch-'A'+27;
    //大写字母占据 27~52 的数组范围
	else return ch-'0'+53; 
    //数字占据 53~62 的数组范围
}

void insert(string s)
{
	int now=start;
	for(int i=0;i<s.length();i++)
	{
		int num=getnumber(s[i]);
		if(!z[now].son[num])
			z[now].son[num]=++cnt;
		z[now].cnt++;
		now=z[now].son[num];
	}
	z[now].cnt++;
	return;
}

void query(string t)
{
	int now=start;
	for(int i=0;i<t.length();i++)
	{
		int num=getnumber(t[i]);
		if(!z[now].son[num]){
			cout<<0<<endl;
			return;
		}
		now=z[now].son[num];
	}
	cout<<z[now].cnt<<endl;
	return;
}

int main()
{
	cin>>T;
	while(T--) //因为有多组测试数据
	//每次查询完都做一下清空太麻烦了
	//我们可以每次重新选一个root,那就用原来的节点数 +1 
	{
		cin>>n>>q;
		for(int i=1;i<=n;i++)
		{
			string s;
			cin>>s;
			insert(s);
		}
		for(int i=1;i<=q;i++)
		{
			string t;
			cin>>t;
			query(t); 
		 }
		 start=++cnt; 
	}
 } 

P10470 前缀统计

  • 题目意思:
    这个题意思挺清晰的,就不重复了。
  • 思路:
    其实就是把上一个题目倒过来了?
    那我们就换个实现方法。
    其实只需要改动一个地方:cnt 的含义。
    我们可以把所有的 \(si\) 放入 \(Trie\) 中,每个字符串的末尾节点的cnt+1,这样 cnt 的含义变成:有多少个 \(si\) 在这里结尾
    每次查询时,答案就把 \(Ti\) 所经过的所有节点的 cnt 值加起来。
  • 代码:
#include<bits/stdc++.h>

using namespace std;

int n,m;
int cnt=1; //我也不知道为啥这里要是 1

struct node{
	int son[30];//只有小写 
	int cnt;//表示到这个点结束的字符串有多少 
}z[1000100]; 

int getnumber(char ch)
{
	return ch-'a'+1;
}

void insert(string s)
{
	int now=1;
	for(int i=0;i<s.length();i++)
	{
		int num=getnumber(s[i]);
		if(!z[now].son[num])
			z[now].son[num]=++cnt;
		//z[now].cnt++;//这里就不需要加了 
		now=z[now].son[num];
	}
	z[now].cnt++;
	return;
}

void query(string t)
{
	int ans=0;
	int now=1;
	for(int i=0;i<t.length();i++)
	{
		int num=getnumber(t[i]);
		if(!z[now].son[num]){
			cout<<ans<<endl;
			return;
		}
		now=z[now].son[num];
		ans+=z[now].cnt;
	}
	cout<<ans<<endl;
	return;
}

int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		string s;
		cin>>s;
		insert(s);
	}
	for(int i=1;i<=m;i++)
	{
		string t;
		cin>>t;
		query(t);
	}
	return 0;
}

完结撒花! 总算学完了

感觉还挺简单的?

posted @ 2024-08-24 15:45  lazy_ZJY  阅读(5)  评论(0编辑  收藏  举报