• AcWing 835. Trie字符串统计


    题目描述


    分析:

    Trie 树是一种能够快速插入查询字符串的多叉树结构。节点的编号各不相同,根节点编号为 0,其他节点用来标识路径,还可以标记单词插入的次数。

    Trie 树最重要的是用 son 这个二维数组模拟多叉树的思想。其中 son[p][u] 表示从节点 p 沿着 u 这条边走到的子节点。边的选择有26(a-z 映射为 0-25) 个。也就是每个节点最多有 26 个分叉。
    另外利用 cnt[p] 来存储以结点 p 结尾的单词的插入次数。
    多次出现的 idx 用来给节点编号。(根据插入图进行正确理解)

    插入
    int son[N][26], cnt[N], idx;
    char str[N];
    
    void insert(char *str)
    {
        int p = 0; // p 指向根节点
        for (int i = 0; str[i]; i ++ ) // 遍历字符串的每一个有效字符
        {
            int u = str[i] - 'a'; // u 为当前字符在(0-25)的映射
            
    		// 从上个节点沿 u 这条边找不到子节点,借助这条边创建子节点,更新 idx 并赋值
            if (!son[p][u]) son[p][u] = ++ idx; 
            p = son[p][u]; // p 指针移动到该子节点,继续插入后面的字符
        }
        cnt[p] ++ ; // 整个字符串插入结束,p 指向最后一个字符的节点,cnt[p] 存储末尾字符出现的次数
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    在空 Trie 中仅有一个根节点,编号为 0。从根开始插,枚举字符串的每个字符:① 如果有子节点(之前有边能通到),则 p 指针顺着之前出现的边走到该节点;② 如果没有子节点,则先创建子节点,根据当前字符映射的值创建边,p 指针再走到该子节点。在单词结束点记录插入次数。
    下面模拟一下插入 “cat” 时树的形状及其参数变化:

    1. 遍历到 ‘c’ 时,c - ‘a’ 为 2,c 又是第 1 个插入的节点 idx 为 1,因此 son[0][2] = 1,也就是说第 1 个节点是根节点沿 2(c - ‘a’) 这条边走到的子节点。
    2. 遍历到 ‘a’ 时,a - ‘a’ 为 0,a 是第 2 个插入的节点 idx 为 2,因此 son[1][0] = 2,也就是说第 2 个节点是第 1 个节点沿 0(a - ‘a’) 这条边走到的子节点。
    3. 遍历到 ‘t’ 时,t - ‘a’ 为 19,t 是第 3 个插入的节点 idx 为 3,因此 son[2][19] = 3,也就是说第 3 个节点是第 2 个节点沿 19(t - ‘a’) 这条边走到的子节点。

    下面画出以此插入 “cat” “car” “busy” “cate” “bus” “car”。用红色的星号来表示字符串结尾处。

    在这里插入图片描述
    这里需要注意的是 idx 的值的变化,只有当前插入的字符串字符是之前没有边到达过的,才会生成节点并更新 idx。这就方便了我们重复字符串的插入后的操作。

    查询
    int query(char *str)
    {
        int p = 0; // p 指向根节点
        for (int i = 0; str[i]; i ++ ) // 遍历字符串的每一个有效字符
        {
            int u = str[i] - 'a'; // u 为当前字符在(0-25)的映射
            if (!son[p][u]) return 0; // 从上个节点沿 u 这条边找不到子节点,该字符串没出现过
            p = son[p][u]; // 出现过,p 指针移动到该子节点,继续判断后面的字符
        }
        return cnt[p]; // 返回以 p 为结尾的字符串的出现的次数
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    查询操作时,从根开始查扫描字符串,简单来说就是有字母 str[i],则走下来一直到词尾,返回插入次数;若无字母,返回0。


    代码(C++)

    #include 
    
    using namespace std;
    
    const int N = 100010;
    
    int son[N][26], cnt[N], idx;
    char str[N];
    
    void insert(char *str)
    {
        int p = 0; // p 指向根节点
        for (int i = 0; str[i]; i ++ ) // 遍历字符串的每一个有效字符
        {
            int u = str[i] - 'a'; // u 为当前字符在(0-25)的映射
            
    		// 从上个节点沿 u 这条边找不到子节点,借助这条边创建子节点,更新 idx 并赋值
            if (!son[p][u]) son[p][u] = ++ idx; 
            p = son[p][u]; // p 指针移动到该子节点,继续插入后面的字符
        }
        cnt[p] ++ ; // 整个字符串插入结束,p 指向最后一个字符的节点,cnt[p] 存储末尾字符出现的次数
    }
    
    int query(char *str)
    {
        int p = 0; // p 指向根节点
        for (int i = 0; str[i]; i ++ ) // 遍历字符串的每一个有效字符
        {
            int u = str[i] - 'a'; // u 为当前字符在(0-25)的映射
            if (!son[p][u]) return 0; // 从上个节点沿 u 这条边找不到子节点,该字符串没出现过
            p = son[p][u]; // 出现过,p 指针移动到该子节点,继续判断后面的字符
        }
        return cnt[p]; // 返回以 p 为结尾的字符串的出现的次数
    }
    
    int main()
    {
        int n;
        cin >> n;
        
        while (n --)
        {
            char op;
            cin >> op >> str;
            if (op == 'I') insert(str);
            else cout << query(str) << endl;
        }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

  • 相关阅读:
    机场、油库、边防、电站都在用的调频无人机干扰设备---TFN MR61
    防火墙命令大全
    request对象对请求体,请求头参数的解析
    N 字形变换
    作为程序员,职业规划需要注意的四个阶段
    Wpf 多指应用开发解析
    面试官:你都工作2年了,这8条查询sql题不会做?
    Cy3 PEG N-羟基琥珀酰亚胺,荧光染料CY3标记聚乙二醇修饰N-羟基琥珀酰亚胺,Cy3-PEG-N-Hydroxy succinimide
    升讯威在线客服系统客服端英文界面的技术实现方法,客户落地巴西圣保罗
    【JVM】初步认识Java虚拟机
  • 原文地址:https://blog.csdn.net/qq_52127701/article/details/126182470