字典树入门及例题展示,弄懂即便入门

陌路散爱 提交于 2019-11-27 04:54:34

博主写的非常给力,我觉得比较通俗易懂,所以冒昧转载,也希望更多的朋友能够轻松弄懂字典树。
在这里插入图片描述
每个字符有很多个分支,打黄色标记的就是字符串的结尾,所以这颗字典树中有哪些字符串呢,“ab”,“ay”,“ayf”,“c”,“cc”,“cd”,其他的枝没有画全。
顺序存储字符串:“ab”“ay”“ayf”“c”“cc”“cd”……(节点编号讲究先到先得)

数组tree[i][j]:代表i节点的第j个儿子的根编号。(获取第几个孩子可以s[i]-‘a’,以图中5节点举例,就是tree[0][2]=5)

数组flag[i]:为true代表到该节点为一个字符串

#include<bits/stdc++.h>
using namespace std;
const int maxn=2e6+5;
int tree[maxn][30],tot=0;
bool flag[maxn];
void add(char *s){
	int root=0,id,len=strlen(s);
	for(int i=0;i<len;++i){
		id=s[i]-'a';
		if(!tree[root][id])	tree[root][id]=++tot;
		root=tree[root][id];
	}
	flag[root]=true;
}
bool find(char *s){
	int root=0,id,len=strlen(s);
	for(int i=0;i<len;++i){
		id=s[i]-'a';
		if(!tree[root][id])	return false;
		root=tree[root][id];
	}
	if(flag[root])	return true;
	else	return false;
}
char a[1005];
int main(){
	add(a);
	find(a);
	return 0;
}

例题归纳:
1.HDU 1251 统计难题
传送门:http://acm.hdu.edu.cn/showproblem.php?pid=1251
题目大意:统计出以某个字符串为前缀的单词数量(单词本身也是自己的前缀).

解题思路:增加一个sum数组把到一个节点 (以该节点为尾的字符串作为前缀) 的字符数存储起来。

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<string>
#include<cstring>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<cmath>
using namespace std;
typedef long long ll;
const ll inf=0x3f3f3f3f;
const int maxn=2e6+5;
int tree[maxn][30],sum[maxn],tot=0;
void add(char *s){
	int len=strlen(s);
	int root=0;
	for(int i=0;i<len;++i){
		int id=s[i]-'a';
		if(!tree[root][id])	tree[root][id]=++tot;
		sum[tree[root][id]]++;
		root=tree[root][id];
	}
}
ll find(char *s){
	ll res=0;
	int root=0,len=strlen(s);
	for(int i=0;i<len;++i){
		int id=s[i]-'a';
		if(!tree[root][id])	return 0;
		root=tree[root][id];
	}
	return sum[root];
}
char tp[maxn];
int main(){
	std::ios::sync_with_stdio(0);
	while(gets(tp)){
		if(tp[0]=='\0')	break;
		add(tp);
	}
	while(scanf("%s",tp)!=EOF){
		printf("%lld\n",find(tp));
	}
	return 0;
}

2.HDU 2072 单词数
传送门:http://acm.hdu.edu.cn/showproblem.php?pid=2072
题目大意:就是统计一篇文章里不同单词的总数。
解题思路:在add的时候存储一下就行了。

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<string>
#include<cstring>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<cmath>
#include<sstream>
using namespace std;
typedef long long ll;
const ll inf=0x3f3f3f3f;
const int maxn=2e6+5;
int tree[maxn][30];
bool flag[maxn];
int ans=0,tot=0;
void add(string s){
	int root=0,id;
	for(int i=0;i<(int)s.size();++i){
		id=s[i]-'a';
		if(!tree[root][id])	tree[root][id]=++tot;
		root=tree[root][id];
	}
	if(!flag[root])	ans++;
	flag[root]=1;
}
int main(){
	std::ios::sync_with_stdio(0);
	string s,tp;
	while(getline(cin,s)){
		if(s=="#")	break;
		stringstream ss(s);
		while(ss>>tp){
			add(tp);
		}
		cout<<ans<<endl;
 
		ans=0;
		for(int i=0;i<=tot;++i){
			flag[i]=0;
			for(int j=0;j<30;++j){
				tree[i][j]=0;
			}
		}
		tot=0;
	}
	return 0;
}

结合set也很简单;

#include<bits/stdc++.h>
using namespace std;
int main() {
	string str1,str2;
	while(getline(cin,str1)) {
		if(str1 == "#")
			break;
		stringstream stream(str1);
		set<string> tp;
		while(stream>>str2) { 
			tp.insert(str2); 
		}
		cout<<tp.size()<<endl; 
	}
	return 0;
}

3.POJ 2001 Shortest Prefixes
传送门:http://poj.org/problem?id=2001
题目大意:求能代表字符串的最小前缀,独一无二,没有歧义的。

解题思路:用sum[root]记录每个节点的数量,只要在find时候走到sum[root]==1的时候说明只有该字符串到过这,那么返回字符串就可以了。

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<string>
#include<cstring>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<cmath>
using namespace std;
typedef long long ll;
const ll inf=0x3f3f3f3f;
const int maxn=2e6+5;
int tree[maxn][30];
bool flag[maxn];
int tot=0,sum[maxn];
void add(char *s){
	int root=0,id,len=strlen(s);
	for(int i=0;i<len;++i){
		id=s[i]-'a';
		if(!tree[root][id])	tree[root][id]=++tot;
		root=tree[root][id];
		sum[root]++;
	}
	flag[root]=1;
}
string find(char *s){
	int root=0,id,i,len=strlen(s);
	string ans="";
	for(i=0;i<len;++i){
		id=s[i]-'a';
		root=tree[root][id];
		ans+=s[i];
		if(sum[root]==1)	return ans;
	}
	return ans;
}
char a[1005][25];
int main(){
	int cnt=0;
	while(scanf("%s",a[cnt++])!=EOF){
		add(a[cnt-1]);
	}
	for(int i=0;i<cnt;++i){
		cout<<a[i]<<" "<<find(a[i])<<endl;
	}
	return 0;
}
 

4.POJ 3630 Phone List
传送门:http://poj.org/problem?id=3630
题目大意:给你很多字符串,问你是不是任何一个字符串都不是其他字符串的前缀,输出YES或NO

解题思路:用sum[root]记录每个节点的数量,如果find一个字符串时,他走过的路上的节点sum值都大于等于2,说明他肯定是某个或者某些字符串的前缀,返回false;

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<string>
#include<cstring>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<cmath>
#include<sstream>
using namespace std;
typedef long long ll;
const ll inf=0x3f3f3f3f;
const int maxn=2e6+5;
int tree[maxn][15],sum[maxn],tot=0;
char a[10005][15];
int T,n;
void add(char *s) {
	int root=0,id,len=strlen(s);
	for(int i=0; i<len; ++i) {
		id=s[i]-'0';
		if(!tree[root][id])	tree[root][id]=++tot;
		root=tree[root][id];
		sum[root]++;
	}
}
bool find(char *s) {
	int root=0,id,len=strlen(s),tp=0;
	for(int i=0; i<len; ++i) {
		id=s[i]-'0';
		root=tree[root][id];
		if(sum[root]>=2)	tp++;
	}
	if(tp==len)	return false;
	else	return true;
}
int main() {
	scanf("%d",&T);
	while(T--) {
		scanf("%d",&n);
		for(int i=0; i<n; ++i) {
			scanf("%s",a[i]);
			add(a[i]);
		}
		bool pd=0;
		for(int i=0;i<n;++i){
			if(!find(a[i])){
				pd=1;
				break;
			}
		}
		if(!pd)	cout<<"YES"<<endl;
		else	cout<<"NO"<<endl;
		
		for(int i=0;i<=tot;++i){
			for(int j=0;j<10;++j){
				tree[i][j]=0;
			}
		}
	}
 
	return 0;
}

5.POJ 1816 Wild Words
传送门:http://poj.org/problem?id=1816
题目大意:给你n个模板字符串和m个待匹配字符串,模板字符串中有‘?’可以匹配任意一个字符,‘’可以匹配0个1个或者多个字符串。问待匹配字符串可以和哪些模板字符串相匹配
解题思路:我们可以将‘
’和‘?’分别放在26和27两个位置,存储字符串的时候,将每个字符串的最后一个节点返回存储起来,find不在和原来一样,而是一个搜索,当搜索的位置==len,而且当且节点为字符串的尾节点(flag标记),那把vis置成true。然后对于‘*’,‘?’,和字符分别搜素下去。

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<string>
#include<cstring>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<cmath>
#include<sstream>
using namespace std;
typedef long long ll;
const ll inf=0x3f3f3f3f;
const int maxn=2e6+5;
int tree[maxn][30],tot=0;
bool flag[maxn],vis[maxn];
int n,m,p[maxn];
int add(char *s) {
	int root=0,id,len=strlen(s);
	for(int i=0; i<len; ++i) {
		if(s[i]=='*')	id=26;
		else if(s[i]=='?')	id=27;
		else id=s[i]-'a';
		if(!tree[root][id])	tree[root][id]=++tot;
		root=tree[root][id];
	}
	flag[root]=1;
	return root;
}
void find(char *s,int nroot,int pos) {
	int root=nroot,id,len=strlen(s);
	if(pos>len)	return;
	if(pos==len && flag[root]) {
		vis[root]=1;
	}
	if(tree[root][26]) { //有*
		for(int j=pos; j<=len; ++j) {
			find(s,tree[root][26],j);
		}
	}
	if(tree[root][27]) { //有?
		find(s,tree[root][27],pos+1);
	}
	id=s[pos]-'a';
	if(s[pos]>='a'&&s[pos]<='z'&&tree[root][id]) {
		find(s,tree[root][id],pos+1);
	}
}
char tp[50];
int main() {
	scanf("%d%d",&n,&m);
	for(int i=0; i<n; ++i) {
		scanf("%s",tp);
		p[i]=add(tp);
	}
	while(m--) {
		scanf("%s",tp);
		find(tp,0,0);
		bool flag=0;
		for(int i=0; i<n; ++i) {
			if(vis[p[i]]) {
				cout<<i<<" ";
				flag=1;
			}
		}
		if(!flag)	cout<<"Not match"<<endl;
		else	cout<<endl;
		for(int i=0; i<n; ++i)	vis[p[i]]=0;
	}
	return 0;
}

6.HDU 1247 Hat’s Words
传送门:http://acm.hdu.edu.cn/showproblem.php?pid=1247
题目大意:给你字符串,输出其中能由其他两个字符串组合而成的字符串
解题思路:建立两颗字典树,一颗正序,一颗反序,开一个vis数组,find的时候正序find一下,如果字符串的第i位有其他字符串flag标记,就vis[i]++,反序find的时候,同样不过是vis[len-i-2]++,标记正序的位置-1,最后从0~len-1,搜一遍,看有没有位置vis[i]>=2,有的话就说明该字符串可以被其他两个字符串组合而成。

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<string>
#include<cstring>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<cmath>
#include<sstream>
using namespace std;
typedef long long ll;
const ll inf=0x3f3f3f3f;
const int maxn=2e6+5;
int tree1[maxn][30],tree2[maxn][30];
//一正序,一反序 
bool flag1[maxn],flag2[maxn];
int vis[maxn];
int tot1=0,tot2=0;
void add1(char *s){
	int root=0,id,len=strlen(s);
	for(int i=0;i<len;++i){
		id=s[i]-'a';
		if(!tree1[root][id])	tree1[root][id]=++tot1;
		root=tree1[root][id];
	}
	flag1[root]=true;
} 
void add2(char *s){
	int root=0,id,len=strlen(s);
	for(int i=0;i<len;++i){
		id=s[i]-'a';
		if(!tree2[root][id])	tree2[root][id]=++tot2;
		root=tree2[root][id];
	}
	flag2[root]=true;
} 
void find1(char *s){
	int root=0,id,len=strlen(s);
	for(int i=0;i<len;++i){
		id=s[i]-'a';
		root=tree1[root][id];
		if(flag1[root])	vis[i]++;
	}
}
void find2(char *s){
	int root=0,id,len=strlen(s);
	for(int i=0;i<len;++i){
		id=s[i]-'a';
		root=tree2[root][id];
		if(flag2[root])	vis[len-i-2]++;
	}
}
char a[50005][100];
set<string> ans; 
int main(){
	int cnt=0;
	while(scanf("%s",a[cnt++])!=EOF){
		add1(a[cnt-1]);
		strrev(a[cnt-1]);
		add2(a[cnt-1]);
		strrev(a[cnt-1]);
	}
	
	for(int i=0;i<cnt;++i){
		int len=strlen(a[i]);
		for(int j=0;j<len;++j)	vis[j]=0;
		find1(a[i]);
		strrev(a[i]);
		find2(a[i]);			
		strrev(a[i]);
		for(int j=0;j<len;++j){
			if(vis[j]>=2){
				string tp=a[i];
				ans.insert(tp);
				break;
			}
		} 
	}
	for(auto it=ans.begin();it!=ans.end();++it){
		cout<<*it<<endl;
	}
	return 0;
}

7.POJ 2513 Colored Sticks
传送门:http://poj.org/problem?id=2513
(字典树/map+并查集+欧拉路径)
题目大意:给你两段有颜色的木棒,只有相同的颜色才能连接起来,问你最后给你的木棒最后能不能拼成一根长木棒
解题思路:这里字典树只是返回了不同颜色的编号,用map<string,int>也是可以的,我们可以记录每个颜色的度,一个木棒的两个颜色可以相连,所以这里可以用并查集将不同的颜色并起来,最后如果,有颜色的祖先和其他不一样,那肯定不能连起来,反之那就要判断欧拉路径 了,如果奇数度的点为0或者2,那这个图就可以一笔走完,即可以连接成一根木棒。

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<string>
#include<cstring>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<cmath>
#include<sstream>
using namespace std;
typedef long long ll;
const ll inf=0x3f3f3f3f;
const int maxn=2e6+5;
int tree[maxn][30],sum[maxn];
int vis[maxn],num[maxn];
int tot=0,cnt=0;
int f[maxn],r[maxn];
int add(char *s) {
	int root=0,id,len=strlen(s);
	for(int i=0; i<len; ++i) {
		id=s[i]-'a';
		if(!tree[root][id])	tree[root][id]=++tot;
		root=tree[root][id];
	}
	if(!vis[root])	vis[root]=++cnt;
	return vis[root];
}
void init() {
	for(int i=0; i<maxn; ++i) {
		f[i]=i;
		r[i]=0;
	}
}
int find(int x) {
	if(x=f[x])	return f[x];
	else return f[x]=find(f[x]);
}
void unite(int x,int y) {
	x=find(x);
	y=find(y);
	if(x==y)	return ;
	if(r[x]<r[y])	f[x]=y;
	else {
		f[y]=x;
		if(r[x]==r[y])	r[y]++;
	}
}
char s1[20],s2[20];
int main() {
	int tp1,tp2;
	init();
	while(scanf("%s%s",s1,s2)!=EOF) {
		tp1=add(s1);
		tp2=add(s2);
		num[tp1]++;
		num[tp2]++;
		unite(tp1,tp2);
	}
	int flag=find(1),tt=0;
	for(int i=1; i<=cnt; ++i) {
		if(num[i]%2)	tt++;
		if(find(i)!=flag) {
			cout<<"Impossible"<<endl;
			return 0;
		}
	}
	if(tt==0||tt==2)	cout<<"Possible"<<endl;
	else	cout<<"Impossible"<<endl;
	
	return 0;
}
 
 
 
 
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!