Trie(算法版)
#include <iostream>
using namespace std;
const int N=100010;
int son[N][26],cnt[N],idx;
//son记录trie数,cnt记录每个词出现的次数,idx记录每个字符所占⽤的下标
//加入字符串
void add(char str[]){
//idx = 0既表⽰根节点也表⽰空节点
int p =0;
for(int i =0;str[i];i++){ //字符串末尾为0
int u = str[i]-'a';
if(!son[p][u]){
son[p][u]= ++idx;//当前字符不存在,增加
}
p=son[p][u]; //向下继续遍历
}
cnt[p]++; //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;//若没找到,直接返回0
}
p=son[p][u];
}
return cnt[p]; //p指向字符串的最后一个字符
}
int main(){
int n;
cin>>n;
while(n--){
char op;
char x[N];
cin>>op>>x;
if(op=='I')add(x);
else cout<<query(x)<<endl;
}
return 0;
}
假设情境 1096:我们有一个 Trie 树,开始时是空的。我们现在要插入以下两个单词:cat 和 car。
初始状态:
- idx:初始值为 0,表示根节点。
- son [N][26]:所有元素初始值为 0,表示 Trie 树是空的。
插入 “cat”:
- 字符 'c':
- 计算索引 u = 'c' - 'a' = 2。
- 检查 son [0][2],因为 Trie 树是空的,所以 son [0][2] 为 0。由于 son [0][2] 为 0,意味着当前节点 0 没有对应字符 'c' 的子节点。因此,我们创建一个新节点 1,并将 son [0][2] 设为 1,表示根节点 0 的 'c' 子节点是节点 1。
- 然后,将 p 更新为 1,即移动到节点 1。
- idx 增加到 1。
- 字符 'a':
- 计算索引 u = 'a' - 'a' = 0。
- 检查 son [1][0],此时 son [1][0] 仍为 0,因为之前还没有插入任何以 'a' 为第二个字符的单词。
- 因此,我们创建一个新节点 2,并将 son [1][0] 设为 2。
- 将 p 更新为 2,现在指向节点 2。
- idx 增加到 2。
- 字符 't':
- 计算索引 u = 't' - 'a' = 19。
- 检查 son [2][19],此时 son [2][19] 为 0,因为还没有插入任何以 't' 为第三个字符的单词。
- 创建新节点 3,并将 son [2][19] 设为 3。
- p 更新为 3,现在指向节点 3。
- idx 增加到 3。
- 此时,已成功插入 “cat”。p 当前指向节点 3,在 cnt [3] 上加 1,表示有一个单词以 't' 结尾。
再插入 “car”:
- 字符 'c':
- 计算索引 u = 'c' - 'a' = 2。
- 检查 son [0][2],此时 son [0][2] 为 1,表示根节点已经有一个 'c' 子节点,指向节点 1。
- 直接将 p 更新为 1,不用创建新节点。
- 字符 'a':
- 计算索引 u = 'a' - 'a' = 0。
- 检查 son [1][0],此时 son [1][0] 为 2,表示节点 1 有一个 'a' 子节点,指向节点 2。
- 将 p 更新为 2。
- 字符 'r':
- 计算索引 u = 'r' - 'a' = 17。
- 检查 son [2][17],此时 son [2][17] 为 0,因为还没有插入任何以 'r' 为第三个字符的单词。
- 创建新节点 4,并将 son [2][17] 设为 4。
- p 更新为 4。
- idx 增加到 4。
- 现在已成功插入 “car”。p 当前指向节点 4,在 cnt [4] 上加 1,表示有一个单词以 'r' 结尾。
总结:
每当我们遇到一个新的字符且当前节点没有对应的子节点时(即 son [p][u] 为 0),我们就需要创建一个新的节点,并更新 son [p][u] 为新节点的编号。同时,idx 自增,用于分配新节点的编号。通过这种方式,Trie 树可以动态地构建,插入和查询操作都能高效完成。