一、基础理论:
字典树,又称单词查找树,Trie树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。二、基本性质:
- 根节点不包含字符,除根节点之外每个子节点都包含一个字符。
- 从根节点开始到某一终止节点,路径上的字符连接起来,就是该终止节点对应的字符串。
- 每个终止节点代表的字符串都是不相同的。
三、优点:
- 快速查找检索,时间复杂度很低。
- 最大限度地节约储存空间。
- 利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高。
四、字典树定义
- int tot;初始化为1,用于给节点编号。
- int trie[maxn][26];字典树的储存,trie[ i ][ j ]表示第i个节点的第j个孩子。
- bool isend[maxn];isend[ i ]用于判断第i个节点是否是某个字符串的结束字符,常用查询整个单词时。
五、基本操作:
- 插入操作(Insert)
假设现在有个这样的Trie树(如图),tot=14(已有14个子节点);
我们往树里再插入这两个单词(bcd、bca)(其实插入时就这两种类型);
①先看第一个bcd:第一个字符‘b’,根节点有这个孩子(‘b’),即trie[root][b]!=0(root=0),那么就不用再添加了,直接往下走,root= trie[root][b];
第二个字符‘c’,巧了也有这个节点trie[root][c]!=0,还是直接往下走,root= trie[root][c];
第三个字符‘d’,树上也有这个节点,trie[root][c]!=0,所以一个新节点也不需要加,那么这个’d’为“bcd”的终止节点,记isend[root]==true(此时root为‘b’的编号,下同),插入完成。②在看第二个字符串“bca”,前两位与第一个字符相同,就不啰嗦了,到第三个节点时,发现这一路上没有‘c’这个节点,trie[root][c]==0(不要说旁边路上有‘c’节点啊,那是别人家的孩子!),那么我们就需要添加新节点‘c’,trie[root][c]现在等于0,我们给他一个编号(++tot),这是它的专属编号了,并且这个节点为终止节点,记isend[root]==true;插入完成。
(其实看懂这些,查找操作也就懂了)
void Insert(char *s,int root) { for(int i=0; s[i]; i++) { int x=s[i]-'a'; if(trie[root][x]==0)//现在插入的字母在之前同一节点处未出现过 { trie[root][x]=++tot;//字母插入一个新的位置,否则不做处理 } root=trie[root][x];//为下个字母的插入做准备 } //isend[root]=true;标志该单词末位字母的尾结点,在查询整个单词时用到 }
- 查找操作(Find)
bool Find(char *s,int root) { for(int i=0; s[i]; i++) { int x=s[i]-'a'; if(trie[root][x]==0)return false;//以root为头结点的x字母不存在,返回0 root=trie[root][x];//为查询下个字母做准备 } return true; //查询整个单词时,应该return isend[root] }
六、例题
- 统计难题 HDU - 1251(题目链接戳这里)板子题(裸)
题意:Ignatius最近遇到一个难题,老师交给他很多单词(只有小写字母组成,不会有重复的单词出现),现在老师要他统计出以某个字符串为前缀的单词数量(单词本身也是自己的前缀).#include <set> #include <map> #include <cmath> #include <stack> #include <queue> #include <vector> #include <string> #include <cstdio> #include <cstring> #include <sstream> #include <iomanip> #include <iostream> #include <algorithm> #define clr(str,x) memset(str,x,sizeof(str)) #define FRER() freopen("in.txt","r",stdin); #define FREW() freopen("out.txt","w",stdout); #define INF 0x3f3f3f3f #define maxn 400000 typedef long long int ll; using namespace std; int trie[maxn][26]; int tot=1; int sum[maxn]; void Insert(char *s) { int n=strlen(s); int root=0; for(int i=0; i<n; i++) { int x=s[i]-'a'; if(trie[root][x]==0) trie[root][x]=tot++; sum[trie[root][x]]++; root=trie[root][x]; } } int Find(char *ss) { int n=strlen(ss); int root=0; for(int i=0; i<n; i++) { int x = ss[i] - 'a'; if(trie[root][x]==0) return 0; root=trie[root][x]; } return sum[root]; } int main() { //FRER() //FREW() char s[12]; while(cin.getline(s,sizeof(s))) { if(strlen(s)==0) break; Insert(s); } while(cin>>s) cout<<Find(s)<<endl; return 0; }
- Xor Sum HDU - 4825(题目链接戳这里)
题意:Zeus 和 Prometheus 做了一个游戏,Prometheus 给 Zeus 一个集合,集合中包含了N个正整数,随后 Prometheus 将向 Zeus 发起M次询问,每次询问中包含一个正整数 S ,之后 Zeus 需要在集合当中找出一个正整数 K ,使得 K 与 S 的异或结果最大。Prometheus 为了让 Zeus 看到人类的伟大,随即同意 Zeus 可以向人类求助。你能证明人类的智慧么?
题解:建树,把数字转化为二进制,二进制不足35位(大于32位即可)的在前面填充上‘0’,存入树中;
求解时,把要求的数字转化为二进制(同上),开始查找(详细可看代码注释)#include <set> #include <map> #include <cmath> #include <stack> #include <queue> #include <vector> #include <string> #include <cstdio> #include <cstring> #include <sstream> #include <iomanip> #include <iostream> #include <algorithm> #define clr(str,x) memset(str,x,sizeof(str)) #define FRER() freopen("in.txt","r",stdin); #define FREW() freopen("out.txt","w",stdout); #define INF 0x3f3f3f3f #define maxn 3000055 typedef long long int ll; using namespace std; char s[40],t[40]; ll trie[3000055][5]; ll len,tot=1; map<int,int> sum; void Insert(ll temp) { ll root=0; for(int i=0; i<35; i++) { int x=s[i]-'0'; if(trie[root][x]==0) trie[root][x]=tot++; root=trie[root][x]; } sum[root]=temp; } ll Get(char *s) { ll root=0; for(int i=0; i<35; i++) { int x=s[i]-'0'; if(trie[root][x]==0) { //若这一节点不存在,则取反去找;(为了保证异或最大) root=trie[root][!x]; } else { //若这一节点存在 if(trie[root][!x])//若取反的节点存在,就去取反的那个节点(为了保证异或最大) root=trie[root][!x]; else//不存在,就继续往下走吧 root=trie[root][x]; } } return sum[root]; } void change(ll x) { memset(s,0,sizeof(s)); memset(t,0,sizeof(t)); for(int i=0; x; i++) { if(x%2==0) s[i]='0'; else s[i]='1'; x/=2; } int n=strlen(s); for(int i=n; i<35; i++) s[i]='0'; for(int i=0,j=34; i<35; j--, i++) t[j]=s[i]; strcpy(s,t); } int main() { int T,n1,m1; ll x; int Case=1; scanf("%d",&T); while(T--) { tot=1; memset(trie,0,sizeof(trie)); sum.clear(); scanf("%d%d",&n1,&m1); for(int i=0; i<n1; i++) { scanf("%lld",&x); change(x); Insert(x); } printf("Case #%d:\n",Case++); for(int i=0; i<m1; i++) { scanf("%lld",&x); change(x); printf("%lld\n",Get(s)); } } return 0; }