About 字典树 Trie

About 字典树 Trie

什么是字典树

Trie 树,即字典树,是一种树形数据结构,本来应该是每个蒟蒻写的第一个数据结构,但由于一些原因,渐渐不被人知晓。

Trie 树的核心思想是空间换时间,利用字符串的公共前缀来降低查询时间的开销以达到提高效率的目的,理论时间复杂度为 \(O(\log n)\)

Trie与二叉查找树不同,键不是直接保存在节点中,而是由节点在树中的位置决定。一个节点的所有子孙都有相同的前缀,也就是这个节点对应的字符串,而根节点对应空字符串。

应用

  1. 字符串检索

    事先将已知的一些字符串(字典)的有关信息保存到 trie 树里,查找另外一些未知字符串是否出现过或者出现频率。

    举例:

    • 给出一个熟词表,以及一篇文章,请你按最早出现的顺序写出所有不在熟词表中的生词。

    • 给出一个词典,再给出一段文本,判断文本中是否含有任何在词典中的单词。

    • 给字符串去重。

  2. 词频统计

    • 有一个文件,里面每一行是一个词,返回频数最高的100个词。

    • 寻找热门查询:搜索引擎会通过日志文件把用户每次检索使用的所有检索串都记录下来。假设目前有一千万个记录,这些查询串的重复度比较高,虽然总数是1千万,但是如果去除重复,不超过3百万个。一个查询串的重复度越高,说明查询它的用户越多,也就越热门。请你统计最热门的10个查询串。

  3. 按字典序排序

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

    比如给你 \(N\) 个互不相同的仅由一个单词构成的英文名,让你将它们按字典序从小到大排序输出。

  4. 字符串最长公共前缀

    Trie树利用多个字符串的公共前缀来节省存储空间,当我们把大量字符串存储到一棵trie树上时,我们可以快速得到某些字符串的公共前缀。

    给出 \(N\) 个小写英文字母串,以及 \(Q\) 个询问,询问某两个串的最长公共前缀的长度是多少。首先对所有的串建立其对应的字母树。此时发现,对于两个串的最长公共前缀的长度即它们所在结点的公共祖先个数,于是,问题就转化为了离线的最近公共祖先(LCA)问题。

  5. 字符串搜索的前缀匹配

    Trie 树常用于搜索提示。如当输入一个网址,可以自动搜索出可能的选择。当没有完全匹配的搜索结果,可以返回前缀最相似的可能。
    Trie树检索的时间复杂度可以做到 \(O(n)\)\(n\) 是要检索单词的长度,如果使用暴力检索,需要 \(O(n^2)\) 的时间复杂度。

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

    如后缀树,AC自动机等

原理

以样例来说

3 3
fusufusu
fusu
anguei
fusu
anguei
kkksc

建出的 Trie 如下。

其中白点为 end。

查询时按字母一个一个 dfs 下去,结束时查询是否为 end。

例题

对于本题,不需要 end,用 cnt 记录次数就成了。

CODE

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=3e6+5;
int tr[N][65];
int cnt[N];
int t,n,q,tot=0;
int change(char c){
	if('a'<=c&&c<='z')	return c-'a';
	if('A'<=c&&c<='Z')	return c-'A'+26;
	return c-'0'+52;
}
void insert(char* s){
	int l=strlen(s),p=0;
	for(int i=0;i<l;i++){
		int c=change(s[i]);
		if(!tr[p][c])
			tr[p][c]=++tot;
		p=tr[p][c];
		cnt[p]++;
	}
	return;
} 
int find(char* s){
	int l=strlen(s),p=0;
	for(int i=0;i<l;i++){
		int c=change(s[i]);
		p=tr[p][c];
		if(!p)	return 0;
	}
	return cnt[p];
}
char s[N];
signed main(){
	cin>>t;
	while(t--){
		tot=0;
		cin>>n>>q;
		for(int i=1;i<=n;i++){
			cin>>s;
			insert(s);
		}
		while(q--){
			cin>>s;
			cout<<find(s)<<endl;
		}
		for(int i=0;i<=tot;i++)
			for(int j=0;j<=64;j++)
				tr[i][j]=0;
		for(int i=1;i<=tot;i++)
			cnt[i]=0;
	}
	return 0;
}

例题

此题正解并非 Trie,仅用于练习。

CODE

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+5;
int tr[N][65];
int ed[N];
int cnt,n,tot=0;
int change(char c){
	if('a'<=c&&c<='z')	return c-'a';
	if('A'<=c&&c<='Z')	return c-'A'+26;
	return c-'0'+52;
}
void insert(char* s){
	int l=strlen(s),p=0;
	for(int i=0;i<l;i++){
		int c=change(s[i]);
		if(!tr[p][c])
			tr[p][c]=++tot;
		p=tr[p][c];
	}
	if(!ed[p])	cnt++;
	ed[p]=1;
	return;
} 
char s[N];
signed main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>s;
		insert(s);
	}
	cout<<cnt;
	return 0;
}

完结撒花

posted @ 2026-01-29 21:34  concert_b  阅读(0)  评论(0)    收藏  举报