字典树,也称前缀树,是一种哈希树的变种。典型应用是统计和排序大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:最大限度地减少无谓的字符串比较,查询效率比哈希表高。主要思想是用时间来换空间。
上面这棵Trie树包含的字符串集合是{in, inn, int, tea, ten, to}。每个节点的编号是我们为了描述方便加上去的。树中的每一条边上都标识有一个字符。这些字符可以是任意一个字符集中的字符。比如对于都是小写字母的字符串,字符集就是’a’-‘z’;对于都是数字的字符串,字符集就是’0’-‘9’;对于二进制字符串,字符集就是0和1。
比如上图中3号节点对应的路径0123上的字符串是inn,8号节点对应的路径0568上的字符串是ten。终结点与集合中的字符串是一一对应的。
图片文字来源:https://blog.youkuaiyun.com/weixin_39778570/article/details/81990417
3个基本性质
1.根节点不包含字符,每条边代表一个字符。
2.从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串。
3.每个节点的所有子节点包含的字符都不相同。
字典树的应用:
1.字符串检索,词频统计,搜索引擎的热门查询
2. 字符串最长公共前缀
给出N 个小写英文字母串,以及Q 个询问,即询问某两个串的最长公共前缀的长度是多少. 解决方案:
首先对所有的串建立其对应的字母树。此时发现,对于两个串的最长公共前缀的长度即它们所在结点的公共祖先个数,于是,问题就转化为了离线(Offline)的最近公共祖先(Least Common Ancestor,简称LCA)问题。
而最近公共祖先问题同样是一个经典问题,可以用下面几种方法:
1. 利用并查集(Disjoint Set),可以采用经典的Tarjan 算法;
2. 求出字母树的欧拉序列(Euler Sequence )后,就可以转为经典的最小值查询(Range Minimum Query,简称RMQ)问题了;
3. 排序
Trie树是一棵多叉树,只要先序遍历整棵树,输出相应的字符串便是按字典序排序的结果。
4. 作为其他数据结构和算法的辅助结构
如后缀树,AC自动机等。
//来源https://blog.youkuaiyun.com/sunhuaqiang1/article/details/52463257
树的构建
首先我们要明白建树常用的数组建树和链表建树,这里因为我们第二维最多就26个字母,所有我们用数组建树,内存最大是所有字符串个数*26.
建树,也就是insert操作,每次插入和询问都是从跟节点开始,我们姑且把根且点当作树的tree[0][]位置,每次都从根节点开始,假设我们第一次先插入in,那个先找i是否从根有连接,没有我们就加i,层数++,再从第一层开始,没有n,再新加n;然后我们插入inn,从根节点找i,找到再从i这个位置找n,再从n这个位置找n,找不到,那么在n的下面一层加个n;
void insert(string s){
int len = s.length();
int pos = 0;
for(int i=0;i<len;i++){
int c = s[i]-'a';
if(tree[pos][c]==0) tree[pos][c]=++tot; //tot是总数,也是我这个点所在的编号
pos = tree[pos][c];
}
end[pos]=true;//这里是这个s字符串的最后一位所在的位置是有,方便查询整个字串是否在集合中
}
树的查询
search操作,和建树类似,也是从根节点开始找,如果有一个位置没找到,直接退出,找到最后一个位置并且最后一个位置在这集合中是存在的,那么我们所找的串就是在这个集合中
bool search(string s){ //查询集合中是否有整个字符串
int len = s.length();
int pos=0;
for(int i=0;i<len;i++){
int c =s[i]-'a';
pos = tree[pos][c];
if(pos==0) return false;
}
return end[pos];
}
/*bool searchqz(string s){ 只查询前缀
int len = s.length();
int pos=0;
int j;
for(j=0;j<len;j++){
int c=s[j]-'a';
pos = tree[pos][c];
if(pos==0) return false;
}
if(j==len) return true; //前缀所有都找完,并且都能找到
}*/
上波总代码
#include<bits/stdc++.h>
using namespace std;
const int maxn = 100100;
int tree[maxn][30];
bool end[maxn];
int tot=1;
void insert(string s){
int len = s.length();
int pos = 0;
for(int i=0;i<len;i++){
int c = s[i]-'a';
if(tree[pos][c]==0) tree[pos][c]=++tot;
pos = tree[pos][c];
}
end[pos]=true;
}
bool search(string s){ //查询集合中是否有整个字符串
int len = s.length();
int pos=0;
for(int i=0;i<len;i++){
int c =s[i]-'a';
pos = tree[pos][c];
if(pos==0) return false;
}
return end[pos];
}
/*bool searchqz(string s){ 只查询前缀
int len = s.length();
int pos=0;
int j;
for(j=0;j<len;j++){
int c=s[j]-'a';
pos = tree[pos][c];
if(pos==0) return false;
}
if(j==len) return true;
}*/
int main(){
int t,q;//t个字符串,q次询问
cin>>t>>q;
string s;
while(t--){
cin>>s;
insert(s);
}
while(q--){
cin>>s;
if(search(s)) printf("有\n");
else printf("无\n");
}
return 0;
}