【Trie 字典树】【模板】 讲解 + 例题 HDU - 1251 - 统计难题 【统计以字符串s为前缀的单词个数】
Trie 字典树讲解
1、Trie的基本操作
- 插入过程(insert)
对于一个单词,从根开始,沿着单词的各个字母所对应的树中的节点分支向下走,直到单词遍历完,将最后的节点进行标记,表示该单词已插入Trie树。 - 查询过程 (query)
同样的,从根开始按照单词的字母顺序向下遍历trie树,一旦发现某个节点标记不存在或者单词遍历完成而最后的节点未被标记,则表示该单词不存在; 若最后的节点被标记了,表示该单词存在。
2、图例
- 假设我们现在有4个单词
- he
- she
- his
- hers
3、性质
- 1.根节点不包含字符,每条边代表一个字符。
- 2.从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串。
- 3.每个节点的所有子节点包含的字符都不相同。
4、约束情况
-
有时候要保证一堆字符串互不是前缀
则只需要判断每个字符串终止节点是否在别的字符串的路径上。 -
有时候要保证单词优先顺序
则按单词顺序对trie树上的节点进行标记。
5、Trie树的应用
-
1、 字符串检索
事先将已知的一些字符串(字典)的有关信息保存到trie树里,查找另外一些未知字符串是否出现过或者出现频率。 -
2、字符串最长公共前缀(LCP)
Trie树利用多个字符串的公共前缀来节省存储空间,反之,当我们把大量字符串存储到一棵trie树上时,我们可以快速得到某些字符串的公共前缀。 -
3、排序
Trie树是一棵多叉树,只要先序遍历整棵树,输出相应的字符串便是按字典序排序的结果。 -
4、作为其他数据结构和算法的辅助结构
如后缀树,AC自动机等
6、01字典树(详情见博文01字典树)
- 01字典树的实现可以看成是把一个数的二进制字符化后插入到一棵一般的字典树中,该树显然是一个只由0和1构成的二叉树
- 01字典树适用于执行异或运算
模板一:数组构成
const int maxnode = 10000;//节点总数(我感觉这个一般得开很大)
const int sigma_size = 26;//出现字符的种类数
struct Tire
{
int sz;//编号
int ch[maxnode][sigma_size];//ch[i][j]表示编号为i的节点的第j个孩子的编号;j是按照字符序存储的,i是编号
int val[maxnode];//记录编号为i的节点信息
Tire()
{
sz = 1;//根节点编号为0
memset(ch[0], 0, sizeof(ch[0]));
}
int idx(char c)
{
return c - 'a';
}
//插入操作
void insert(char *s, int v)
{
int u = 0, n = strlen(s);//u=0表示从根节点开始走
for(int i = 0; i < n; i++)
{
int c = idx(s[i]);
if(!ch[u][c])//第u个节点并没有c字符的儿子
{
memset(ch[sz], 0, sizeof(ch[sz]));//开辟一个新的节点
val[sz] = 0;//清空
ch[u][c] = sz++;//把新开辟的节点的编号放在 第u个节点的c字符的儿子里
}
u = ch[u][c];//从u走到c字符儿子的编号,继续往下走
//#1 : val[u]++;//标记每一种前缀出现了几次
}
// #2 : val[u] = v;//用最后一个字符存储附加信息,如单词结束
}
};
//查询操作
int query(char s[])
{
int u = 0, len = strlen(s);
for(int i = 0; i < len; i++)
{
int c = idx(s[i]);
if(!ch[u][c])//字典树中未存储字符串S
return 0;
u = ch[u][c];//往下走
}
return val[u];//成功找到,返回附加信息
}
模板二:指针链表构成
typedef struct trie
{
int val;
trie *next[30];
bool exist;
/*写了构造函数就不用写CreateTrie()函数了
trie()
{
val = 0;
memset(next, 0, sizeof(next));
exist = false;
}
*/
};
trie * CreateTrie()
{
trie *node = new trie;
node->val = 0;
node->exist = false;
memset(node->next, 0, sizeof(node->next));
return node;
}
void InsertTrie(trie *root, char *word)
{
trie *node = root;
char *p = word;
int id;
while(*p)
{
id = *p - 'a';
if(node->next[id] == NULL)
{
node->next[id] = CreateTrie();
}
node = node->next[id];
//#1 : node->val++;
p++;
}
//#2 : node->val = 1;
node->exist = true;
}
int FindTrie(trie *root, char *str)
{
int len = strlen(str);
trie *p = root;
for(int i = 0; i < len; i++)
{
int id = str[i] - 'a';
p = p->next[id];
if(p == NULL)
return 0;
}
return p->val;
}
void DeleteTrie(trie * node)
{
for(int i = 0; i < 30; i++)
{
if(node->next[i])
DeleteTrie(node->next[i]);
}
delete node;
}
//main函数中使用时, 先创建一个根节点
trie *root = CreateTrie();
例题:HDU - 1251 - 统计难题
Ignatius最近遇到一个难题,老师交给他很多单词(只有小写字母组成,不会有重复的单词出现),现在老师要他统计出以某个字符串为前缀的单词数量(单词本身也是自己的前缀).
Input
输入数据的第一部分是一张单词表,每行一个单词,单词的长度不超过10,它们代表的是老师交给Ignatius统计的单词,一个空行代表单词表的结束.第二部分是一连串的提问,每行一个提问,每个提问都是一个字符串.
注意:本题只有一组测试数据,处理到文件结束.
Output
对于每个提问,给出以该字符串为前缀的单词的数量.
Sample Input
banana
band
bee
absolute
acm
ba
b
band
abc
Sample Output
2
3
1
0
思路:
读入每一个单词,记录在同一个Trie中,修改一下插入操作,使每个字母结点的val均记录,以该字母为末尾的字符子串是多少个单词的前缀,读入询问的字符串,在Trie中询问得到val值即可。
另外,这题编译器要选C++,选G++会MLE。数组要开的比较大。
AC代码一:(数组型)
#include <iostream>
#include <stdio.h>
#include <string>
#include <string.h>
#include <set>
#include <map>
using namespace std;
const int sigma_size = 28;
const int maxnode = 500005;
struct Tire
{
int ch[maxnode][sigma_size];
int val[maxnode];
int sz;
Tire()
{
sz = 1;
memset(ch[0], 0, sizeof(ch[0]));
}
int idx(char c)
{
return c - 'a';
}
void insert(char s[])
{
int u = 0, len = strlen(s);
for(int i = 0; i < len; i++)
{
int c = idx(s[i]);
if(!ch[u][c])
{
memset(ch[sz], 0, sizeof(ch[sz]));
val[sz] = 0;
ch[u][c] = sz++;
}
u = ch[u][c];
val[u]++;
}
}
int query(char s[])
{
int u = 0, len = strlen(s);
if(len > 10)
return 0;
for(int i = 0; i < len; i++)
{
int c = idx(s[i]);
if(!ch[u][c])
return 0;
u = ch[u][c];
}
return val[u];
}
};
const int mx = 100;
char str[mx];
int main()
{
Tire t;
while(gets(str) && str[0])
{
t.insert(str);
}
while(gets(str))
{
printf("%d\n", t.query(str));
}
return 0;
}
AC代码二:(指针链表型)
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <string>
#include <math.h>
#include <algorithm>
using namespace std;
typedef struct trie
{
int val;
trie *next[30];
bool exist;
};
trie * CreateTrie()
{
trie *node = new trie;
node->val = 0;
node->exist = false;
memset(node->next, 0, sizeof(node->next));
return node;
}
void InsertTrie(trie *root, char *word)
{
trie *node = root;
char *p = word;
int id;
while(*p)
{
id = *p - 'a';
if(node->next[id] == NULL)
{
node->next[id] = CreateTrie();
}
node = node->next[id];
node->val++;
p++;
}
node->exist = true;
}
int FindTrie(trie *root, char *str)
{
int len = strlen(str);
trie *p = root;
for(int i = 0; i < len; i++)
{
int id = str[i] - 'a';
p = p->next[id];
if(p == NULL)
return 0;
}
return p->val;
}
int main()
{
trie *root = CreateTrie();
char word[105];
int flag = 1;
while(gets(word))
{
if(strlen(word) == 0)
{
flag = 0;
continue;
}
if(flag)
{
InsertTrie(root, word);
}
else
{
int n = FindTrie(root, word);
printf("%d\n", n);
}
}
return 0;
}