引
问题就是给出n个单词,询问q次,看某单词是否在这些单词中,单词最长为l。如果使用暴力遍历的话,复杂度可达到O(n*q*l),很显然,一般会超时的。因此,我们发现了Trie树复杂度降为O((n+q)*l)。
Trie树是个什么样的数据结构
int son[N][26], cnt[N], idx;
// 0号点既是根节点,又是空节点
// son[][]存储树中每个节点的子节点
// cnt[]存储以每个节点结尾的单词数量
idx:插入顺序标号,全局变量
son[p][x]=i:p表示新插入字母的前一个字母的标号,x表示新字母-'a'的值,i指新插入字母的标号。
cnt[i]:i表示字母标号,cnt[i]表示该字母作为结尾的同一单词出现的个数。
举个例子吧!
依次插入abc,ac,bb
就节点6而言:i=6毋庸置疑,b的前一个单词是b,6的前置节点是5,所以p=5,b-a=1,所以x=1,因此son[5][1]=6。且b是最后一个单词,所以cnt[6]++,cnt[6]=1.
咋写代码
插入新单词
- 初始位置标在头结点上。
- 提取第一个字母,看是否在第一层子节点中(?son[0][x]),如果没有,就插入,新节点的标号是++idx;如果有直接下一步。
- 位置下移到新节点,提取第二个字母,重复上一步操作。
- 遍历完整个单词以后,最后一个字母cnt++
这里的p很像一个箭头指在当前的节点上,可以把它看做一个指针
// 插入一个字符串
void insert(char str[])
{
int p = 0;
for (int i = 0; str[i]; i ++ )
{
int x = str[i] - 'a';
if (!son[p][x]) son[p][x] = ++ idx;
p = son[p][x];
}
cnt[p] ++ ;
}
譬如:在上面的基础上,插入bm
查询单词出现次数
int find(char str[])
{
int p = 0;
for (int i = 0; str[i]; i ++ )
{
int x = str[i] - 'a';
if (!son[p][x]) return 0;
p = son[p][x];
}
return cnt[p];
}
与插入大同小异!由于son[][]是全局变量,初始化都是0,所以当son[p][x]==0时,就说明该字母不在树里,因此就无法查询。