Trie树讲解 + AcWing 835. Trie字符串统计

Trie(字典树)是一种用于实现字符串快速检索的多叉树结构。Trie 的每个节点都拥有若干个字符指针,若在插入或检索字符串时扫描到一个字符 c,就沿着当前节点的 c 字符指针,走向该指针指向的节点。

Trie树可以 高效 支持 两个操作

  • 存储字符串集合
  • 查询字符串集合

有一个 经验:凡是用到Trie树的题目,一般来说给出的字符串要么全是小写字母,要么全是大写字母,要么都是数字,要么全是01,总之字母类型不是很多。

下面来看看它是如何高效存储字符串的:

初始化

一棵空Trie仅包含一个根节点,该点的字符指针 均指向空

插入

(1)过程分析

当需要 插入一个字符串S 时,我们令一个指针Р起初指向根节点。然后,依次扫描S中的每个字符c:

  • 1.若Pc字符指针 指向一个已经存在的节点Q,则 P= Q
  • 2.若Pc字符指针 指向空,则 新建一个节点Q,令Pc字符指针 指向Q,然后 令P=Q

s中的字符 扫描完毕时,在当前节点P标记它是一个字符串的末尾

下图展示了 向Trie树中插入字符串过程,每插入一个字符串 都会标记一次
在这里插入图片描述
在上图所示的例子中,需要插入和检索的字符串都由小写字母构成,所以Trie 的每个节点具有26个字符指针,分别为az

上图展示了在一棵空Trie中依次插入“cab"、“cos"、"car"、“cat”、" cate”“rain” 后的 Trie 的形态,灰色 标记了 单词的末尾节点

可以看出在Trie 中,字符数据都体现在树的边(指针)上树的节点仅保存一些额外信息,例如 单词结尾标记等。

(2)空间复杂度

O(NC),其中 N是节点个数(即我们想要存储的字符串的最大长度),C是字符集的大小(如:26个字母则 C = 26,Trie树中 每个节点最多向外生26条边)。

(3)时间复杂度

树的高度成正相关

(4)代码片段
const int N = 1e7+10;
inr son[N][26]; //存储Trie树中每个点所有儿子
int cnt[N]; //存储以当前这个节点结尾的单词有多少个
int idx; //当前用到了哪个下标,下标是0的节点既是根节点,又是空节点

void insert(string s)
{
    int p = 0; //从根节点开始
    for(int i=0; i<s.size(); ++i) //从前往后遍历所插字符串的每个字符
    {
        int u = s[i] - 'a'; //每次将遍历到的当前字符对应的子节点编号求出(将小写字母 a~z 映射成 0~25)
        if(!son[p][u]) son[p][u] = ++idx; //如果当前节点 p 不存在 u 这个儿子,就创建出来
        p = son[p][u]; //走到下一个点
    }
    cnt[p]++; //结束的时候 p 指向的点 对应 插入字符串的最后一个字符,表示以该点结尾的单词数量多了一个
}

查询

(1)过程分析

当需要 查询一个字符串STrie中是否存在 时,我们令一个指针Р起初指向根节点,然后依次扫描S中的每个字符c:

  • 1.若Pc字符指针 指向空,则说明S没有被插入过Trie结束查询
  • 2.若Pc字符指针 指向一个已经存在的节点Q,则 P =Q

S中的字符 扫描完毕时,若当前节点Р 被标记为一个字符串的末尾,则说明STrie存在否则 说明 s没有被插入过Trie

(2)时间复杂度

树的高度成正相关

(3)代码片段
int query(string s) //返回的值是字符串出现的次数
{
    int p=0; //从根节点开始
    for(int i=0; i<s.size(); ++i)
    {
        int u = s[i]-'a'; //每次将遍历到的当前字符对应的子节点编号求出(将小写字母 a~z 映射成 0~25)
        if(!son[p][u]) return 0; //如果当前节点 p 不存在 u 这个儿子,说明当前集合不存在这个单词,直接返回 0 即可
        p = son[p][u]; //否则的话就走到下一个点
    }
    return cnt[p]; //返回以 p 结尾的单词数量
}

例题 AcWing 835. Trie字符串统计

在这里插入图片描述
在这里插入图片描述

代码:

#include<bits/stdc++.h>

using namespace std;
const int N = 1e5+10;
int son[N][26], cnt[N], idx;
int n;

void insert(string s)
{
    int p = 0;
    for(int i=0; i<s.size(); ++i)
    {
        int u = s[i]-'a';
        if(!son[p][u]) son[p][u]  = ++idx;
        p = son[p][u];
    }
    cnt[p]++;
}

int query(string s)
{
    int p = 0;
    for(int i=0; i<s.size(); ++i)
    {
        int u = s[i]-'a';
        if(!son[p][u]) return 0;
        p = son[p][u];
    }
    return cnt[p];
}

int main()
{
    cin>>n;
    string x;
    while(n--)
    {
        char op[2];
        cin>>op>>x;
        if(*op=='I') insert(x);
        else printf("%d\n", query(x));
    }
    
    return 0;
}
  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值