7.9 字典树

175 阅读3分钟

概念:字典树(TrieTree),是一种树形结构,典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串,如01字典树)。主要思想是利用字符串的公共前缀来节约存储空间。很好地利用了串的公共前缀,节约了存储空间。字典树主要包含两种操作,插入和查找。

比如,我们要怎么用树存下单词"abc",“abb”,“bca”,"bc"呢?见图

image.png

在图中,红点代表有一个以此节点为终点的单词。然后,我们如果要查找某个单词如s=“abc”,就可以这样:

image.png

在这里,s=“abc” 的每一个字母都在树中被查到了,并且最后一个点是红色代表有一个在此结束的单词,查询成功。而 s=“bb” 的第二个字母没有在相应位置被查到,因此"bb"不在字典中。至于s=“ab” 虽然单词中每个点都被查到了,但是由于结尾的字母在树中没有标红,因此也是不在字典中。

对于字典树的每次查找,时间复杂度为log级别,比暴力快多了。

字典树主要是可以替代哈希表,完成一些工作。字典树更快。

字典树的链表实现很简单,就是简单的模拟,这里就不去写了。

字典树的数组实现的模板:(这也是最常用的)

//trie字典树
#define N 100010
//son表示指向孩子结点的位置,cnt表示标记的字符串的结尾,idx表示用到的位置
int son[N][26], cnt[N], idx;
 
//插入
void insert(char str[])
{
	int p = 0;
	for (int i = 0; str[i]; i++)//因为字符串是以\0结尾
	{
		int u = str[i] - 'a';//获取放入的列数
		if (!son[p][u])son[p][u] = ++idx;//走完和其他字符串相同的地方
		p = son[p][u];  //再让p指向下一个行
	}
	cnt[p]++;//标记结尾
}
//查询操作
int query(char str[]) 
{
	int p = 0;
	for (int i = 0; str[i]; i++)
	{
		int u = str[i] - 'a';
		if (!son[p][u])return 0;  //如果此时没有找到该字符就直接返回
		p = son[p][u];           //如果找到了就继续向下寻找
	}
	return cnt[p];               //判断是否有这个结尾的字符串
}

例题:(这种数据结构题,我学习的时候一般只写三道题,以后就是重复的练习)

P8306 【模板】字典树

#include<bits/stdc++.h>
#include<string>
using namespace std;


int idx;
int son[3000005][65];
long long cnt[3000005];
char s[3000005];
int getnum(char x){
    if(x>='A'&&x<='Z')
        return x-'A';
    else if(x>='a'&&x<='z')
        return x-'a'+26;
    else
        return x-'0'+52;
} 
	void insert(char str[]){
		int root = 1,len = strlen(str);
		for(int i=0;i<len;i++){
			int a = getnum(str[i]);
			if(!son[root][a]) son[root][a] = ++idx;
			root = son[root][a];
			cnt[root]++;
		}
	}
	int search(char str[]){
		int root = 1,len = strlen(str);
		for(int i=0;i<len;i++){
			int a = getnum(str[i]);
			if(!son[root][a]) return 0;
			root = son[root][a];
		}
		return cnt[root];
	}

void snowball(){
	int s_num,t_num;
	scanf("%d%d", &s_num, &t_num);
	for(int i = 0;i < s_num;i++) scanf("%s",s),insert(s);
	for(int i = 0;i < t_num;i++) scanf("%s",s),printf("%d\n",search(s));

}
int main() {
	int n;
	scanf("%d",&n);
	for(int i=0; i<n; i++) {
		for(int k = 1;k <=idx;k++) {
		cnt[k] = 0;
		for(int j = 0;j < 70;j++) son[k][j] = 0;
	}
		idx = 1;
		snowball();
	}
	return 0;
}

超时了呜呜呜~~~

P5149 会议座位

#include<bits/stdc++.h>
using namespace std;

#define N 500005
//son存当前点的所有儿子,cnt表示以当前点结尾的单词的个数,idx指向当前结点 
//下标是0 的点,既是根结点,又是空结点 
int son[N][52];
int cnt[N];
int f[N];
int temp[N];
int idx = 1;
long long ans = 0;
void insert(string str,int k){
	int len = str.length();
	int root = 1;
	for(char s:str){
		int a = s - 'A';
		if(!son[root][a]) son[root][a] = ++idx;
		root = son[root][a];
	}
	cnt[root] = k;//最后一个节点放入对应的排序序号;
}
int find(string str){
	int root = 1;
	for(char s:str){
		int a =  s - 'A'; //这里其实有问题,不知道为啥没报错
		if(!son[root][a]) return 0;
		root = son[root][a];
	}
	return cnt[root];
}
void merge_sort(int low,int high){
	if(low >= high) return;
		int mid = (low + high) >> 1;
		merge_sort(low,mid);
		merge_sort(mid+1,high);
		int i,j,k;
		for(i = low,j = mid + 1,k = 0;i <= mid&&j <= high;){
			if(f[i] > f[j]) ans += mid - i + 1,temp[++k] = f[j++];
			else temp[++k] = f[i++];
		}
		while(i <= mid) temp[++k] = f[i++];
		while(j <= high) temp[++k] = f[j++];
		for(int rr = 1;rr <= k;rr++)
			f[low++] = temp[rr];
	
}
int main() {
	int n;
	cin>>n;
	string s;
	for(int i=0; i<n; i++){
		cin>>s;
		insert(s,i+1);
	}
	for(int i=1; i<=n; i++){
		cin>>s;
		f[i] =find(s);
	}
	merge_sort(1,n);
	cout<<ans;
	return 0;
}