含义:
Trie树一般指字典树,又称单词查找树,Trie树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高。
假设我们有五个字符串,code、cook、five、file、fat。现在需要在里面多次查找某个字符串是否存在。如果每次查找是拿查找的字符串和这5个字符串一次进行匹配,那么效率就会比较低。
如果将这五个字符串组织成字典树,就是下面这个图,查找起来会更快。
性质:
- 根节点不包含字符,除根节点外每一个节点都只包含一个字符
- 从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串
- 每个节点的所有子节点包含的字符都不相同
基本操作:
查找、插入和删除,当然删除操作比较少见。
Trie树构造
Trie树的插入操作
Trie树的插入操作:是将单词的每个字母逐一插进Trie树中。
插入前先看该字母对应的结点是否已经存在
,若存在共享结点,则不用创建对应的结点,若不存在,就需要创建一个新结点。最后,我们在最后一个字母结点进行标记
(即图中的黄色结点代表标记)。(比如插入cook后,要对k所在的结点进行标记,代表c->o->o->k这条路径上的所有结点可以组成一个单词cook)
Trie树的查询操作:比如我们查找code,可以将要查找的单词code分割成单个的字母c、o、d、e,然后从Trie树的根结点开始匹配,
直到每个结点都存在(需在一条路径上)且最后一个结点被标记
,才算查找成功。如图所示,绿色的路径就是在Trie树中匹配的路径。
835. Trie字符串统计
题目描述
维护一个字符串集合,支持两种操作:
“I x”向集合中插入一个字符串x;
“Q x”询问一个字符串在集合中出现了多少次。
共有N个操作,输入的字符串总长度不超过 10^5,字符串仅包含小写英文字母。
输入格式
第一行包含整数N,表示操作数。
接下来N行,每行包含一个操作指令,指令为”I x”或”Q x”中的一种。
输出格式
对于每个询问指令”Q x”,都要输出一个整数作为结果,表示x在集合中出现的次数。
每个结果占一行。
数据范围
1≤N≤2∗10^4
输入样例:
5
I abc
Q abc
Q ab
I ab
Q ab
输出样例:
1
0
1
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int son[N][26],cnt[N],idx;
char str[N];
//son[N][26]:存的值是儿子结点对应的idx
//son[N][26]:一维下标是父节点的idx,二维下标是这个父节点的直接子节点的str[i]-'a'的值
//cnt[N]:以'abc'为例, 最后一个字符'c'对应的idx作为cnt的下标。
//cnt[N]:存的值是该以该idx结尾的字符串的个数,即有几个'abc'字符串
//idx代表当前字符的编号,根结点为0
void insert(char str[]) //插入字符串
{
int p=0;
for(int i=0;str[i];i++) //对这个单词的每个字母依次进行插入
{
int u=str[i]-'a'; //将a~z转换成0~25
if(!son[p][u]) son[p][u]=++idx; //如果这个字母没有就添加一个,给它一个编号
p=son[p][u]; //继续检索
}
cnt[p]++; //单词'abc'插入之后,将最后一个字符c的cnt++,代表以c结尾的单词多了一个
}
int query(char str[]) //查询字符串出现的次数
{
int p=0;
for(int i=0;str[i];i++)
{
int u=str[i]-'a'; //将a~z转换成0~25
if(!son[p][u]) return 0; //有某个结点不存在直接结束
p=son[p][u]; //这个结点存在就继续往下走
}
return cnt[p];
}
int main()
{
int n;
cin>>n;
while(n--)
{
char op;
cin>>op>>str;
if(op=='I') insert(str);
else cout<<query(str)<<endl;
}
return 0;
}
举个例子:对①~⑤个字符串依次进行插入,那么每个字符对应的idx就为图中绿色的数字。
这题建议自己画一个字典树,然后按照你画的字典树把代码顺一遍,这样会好理解些…我是这么看的
AcWing 143. 最大异或对
#include<iostream>
using namespace std;
const int N = 31*1e5 + 10;
int a[N], son[N][2], idx;
void insert(int x)
{
int p = 0;
for(int i=30; i>=0; i--) //先插入最高位
{
int u = x>>i & 1;
if(!son[p][u]) son[p][u] = ++idx;
p = son[p][u];
}
}
int query(int x)
{
int p = 0, res = 0;
for(int i=30; i>=0; i--)
{
int u = x>>i & 1;
if(son[p][!u])
{
res = res*2 + !u;
p = son[p][!u];
}
else
{
res = res*2 + u;
p = son[p][u];
}
}
return res;
}
int main()
{
int n;
cin >> n;
int res = 0;
for(int i=0; i<n; i++) //对于每一个a[i]先插入,再查询 a[0~i-1] 的数
{
cin >> a[i];
insert(a[i]);
int t = query(a[i]); // 查询 a[0~i-1] 的数
res = max(res,t ^ a[i]);
}
cout << res;
}
方法一:
#include<iostream>
#include<string.h>
using namespace std;
const int N = 5e4+20;
int son[N][30],cnt[N],idx;
char str[N];
char ch[N][110];
void insert(char str[])
{
int p = 0;
for(int i=0; str[i]; i++)
{
int u = str[i] - 'a';
if(!son[p][u]) son[p][u] = ++idx;
p = son[p][u];
}
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]>=1;
}
int main()
{
int b = 0;
char l[110],r[110];
while(cin >> ch[b])
{
insert(ch[b++]);
}
for(int i=0; i<b; i++)
{
int len = strlen(ch[i]);
if(len<=1) continue;
for(int j=1; j<len; j++)
{
strcpy(r,ch[i]+j); //右半部分
strcpy(l,ch[i]);
l[j] = '\0'; //左半部分
if(query(l) && query(r))
{
cout<<ch[i]<<endl;
break;
}
}
}
}
方法二:
#include<iostream>
#include<map>
using namespace std;
int main()
{
map<string,int> m;
string x;
while(cin >> x)
{
m[x] = 1;
}
for(map<string,int>::iterator it=m.begin(); it!=m.end(); it++)
{
string a = it->first;
for(int i=1; i<a.size(); i++)
{
string a1(a,0,i);//从0开始的i个字符
string a2(a,i); //从i开始的所有字符
if(m.find(a1) != m.end() && m.find(a2) != m.end())
{
cout<<a<<endl;
break;
}
}
}
}
借鉴: 可以看看下面这篇,写的很详细。
看动画轻松理解「Trie树」