Trie树,也叫字典树,前缀树。是一种用于实现字符串快速检索的多叉树数据结构。它支持两种操作:
- 在树中插入一个字符串
- 字符串检索
Trie树的每个节点都拥有若干个字符指针,它可以用来插入字符串或者进行字符串检索。
Trie树的基本操作 初始化Trie树刚开始时只有一个根节点,它的所有指针均指向NULL。
根节点并不存储信息,只作为树的标识,进行字符串插入和检索时从根节点进入并扫描其节点,原因也容易理解,根节点只有一个,所以只能存储一个字符,如果要在根节点存入字符的话,就需要开多棵Trie树分别存入不同的开头字母,这显然有些多此一举了。
假设我们此时需要插入一个字符串s,起初我们令一个指针p指向根节点,依次扫描s的每个字符c,此时我们会发现有两种情况:
- p的c字符指针指向了一个已存在的节点q,则p直接走到q,然后更新p,即p = q
- p的c指针指向空,此时我们新建一个节点q,然后将p走到q,然后更新p,即p = q
当字符串s扫描完毕时,在当前节点打上标记。因为如果不打上标记的话,假如此时存入了一个字符串 b a t m a n batman batman,然后我们要查询是否有存入过 b a t bat bat,没有标记的话,就无法判断是否插入过 b a t bat bat了。
检索检索和插入很像。
假设我们此时需要检索一个字符串s,起初我们令一个指针p指向根节点,依次扫描s的每个字符c,此时我们会发现有两种情况:
- p的c字符指针指向了一个已存在的节点q,则p直接走到q,然后更新p,即p = q
- p的c指针指向空,则代表该字符串没有插入过,此时我们结束检索
当s中的字符扫描完毕时,若当前p有被标记过,则说明s在Trie中存在,否则不存在。
示例假设此时我们需要插入一个字符串cat
-
创建指针p进入根节点,扫描其子节点,发现并没有c这个节点,因此创建一个c节点,然后将p移动到c
-
p扫描c的子节点,发现并没有a字符,创建并移动到a
-
p扫描a的子节点,发现并没有t,创建并移动到t
-
此时我们发现字符串已经扫描完毕,标记p
假设我们此时还需要插入一个字符串cab
-
创建指针p进入根节点,扫描其子节点有没有字符c,发现是有的,直接进入c
-
扫描p的子节点有没有a,发现是有的,直接进入a
-
扫描p的子节点,看看有没有b,发现并没有,创建并将p移动到节点b
-
此时发现字符串cab已扫描完毕,标记p
其余也是类似,此处就不再过多说明。
维护一个字符串集合,支持两种操作:
I x:向集合中插入一个字符串 x;
Q x :询问一个字符串在集合中出现了多少次。
共有N个操作,输入的字符串总长度不超过
1
0
5
10^5
105,字符串仅包含小写英文字母。
第一行包含整数N,表示操作数。
接下来N行,每行包含一个操作指令,指令为I x或Q x中的一种。
对于每个询问指令Q x,都要输出一个整数作为结果,表示x在集合中出现的次数。
每个结果占一行。
1 ≤ N ≤ 2 ∗ 1 0 4 1≤N≤2∗10^4 1≤N≤2∗104
输入样例:5 I abc Q abc Q ab I ab Q ab输出样例:
1 0 1解题思路 思路1:unordered_map(哈希表)
此题只有插入和查询字符串次数两个操作,那么用哈希表显然是一种快准狠的方法,在此就不多赘述了,读者感兴趣可以自己写写
思路1代码#include思路2:#include #include using namespace std; int n; unordered_map mp; int main(){ cin >> n; while(n --){ char op[2]; string str; cin >> op >> str; if(op[0] == 'I') mp[str] ++; else printf("%dn",mp[str]); } return 0; }
使用Trie树,此时我们会发现询问操作是询问我们字符串出现次数,那么插入结束时的标记操作就可以转换为计数操作(插入结束时在尾部 + 1),其余并没有什么变化。
代码#include#include using namespace std; const int N = 100010; int n; char str[N]; int son[N][26],cnt[N],idx; void insert(char str[]){ // 插入字符串str int p = 0; // 创建p指向根节点 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没有u这个子节点,则创建出该子节点 p = son[p][u]; // 将p移动到该子节点中 } cnt[p] ++; // 表示该字符串出现次数 + 1 } int query(char str[]){ // 检索 int p = 0; // 创建指针p指向根节点 for(int i = 0; str[i]; i ++){ // 遍历待检索字符串 int u = str[i] - 'a'; // 将 a ~ z 映射为 0 ~ 25 if(!son[p][u]) return 0; // 如果p没有u这个子节点,说明没有该字符串,返回0 p = son[p][u]; // 将p移动到该子节点中 } return cnt[p]; // 返回字符串出现的次数 } int main(){ scanf("%d",&n); while(n --){ char op[2]; scanf("%s%s",op,str); if(op[0] == 'I') insert(str); // 插入 else printf("%dn",query(str)); // 检索 } return 0; }



