Trie


前言

复习acwing算法基础课的内容,本篇为讲解基础算法:Trie,关于时间复杂度:目前博主不太会计算,先鸽了,日后一定补上。


一、Trie

能高效的插入和查找字符串的数据结构    -------yxc

我们通过一个例子来说明什么是Trie:
例如,现我们要存储这么几个字符串:

abcdef
abedf
acef
cbde
cbdf
abc

我们按照如下方式存储:

首先我们定义一个树根:
在这里插入图片描述
然后把树根的初始坐标设为0,紧接着我们开始例子的插入操作:
插入abcdef
询问树根是否有a这个子节点,如果没有,则创建:
在这里插入图片描述
然后看a这个子节点有没有b这个子节点,如果没有则创建,剩下的也以此类推,所以插入abcdef后的图如下图所示:
在这里插入图片描述
紧接着我们插入abedf
还是从root开始,看有没有a的子节点,发现有,那么就就走到a这个子节点
接下来看有没有b这个字节点,发现有,那么走到b这个子节点
接下来看有没有e这个字节点,发现没有,那么创建这个子节点
在这里插入图片描述
接下来和之前的一样,创建d子节点和f子节点
在这里插入图片描述
接下来插入acef,和之前一样,插入后结果为
在这里插入图片描述
接下来cbde,一样从root开始,发现没有c子节点,则创建
在这里插入图片描述
接下来插入cbdf,和之前一样,最终结果为
在这里插入图片描述
插入abc
我们还需要一个操作就是标记一下每一个插入的串的结尾,这样我们做查询操作的时候才能知道到底有没有这个串,比如,如果我们不标记一下结尾的话,那么对于我们插入abc这个串的时候,我们这个Trie树种已经有abc这个子串了,那么我们如果要查询是否有abc这个子串的时候,我们才能知道是真的有这个串,而不是abcdef的子串,故我们标记为:

在这里插入图片描述

从这个表中,我们就储存了所有的字符串,并且标记了每一个串的结尾,那么我们在查询的时候从root开始遍历到这个标记的结尾的话,就证明存在这个子串


二、例题,代码

1.AcWing 835. Trie字符串统计

本题链接:AcWing 835. Trie字符串统计
本博客给出本题截图:

在这里插入图片描述

关于本题:

我们在操作过程中,需要一个son[N][26]数组,son[p][u]的作用就是代表着以p为根,u为子节点,我们在插入的过程中,如果以p为根的u的子节点不存在的话,那么就把它创建出来,然后让p = son[p][u],如果存在p为根的u的子节点的话,直接让p = son[p][u];

if (!son[p][u]) son[p][u] = ++ dix;   //为什么不是idx ++;
//idx一开始指向,就是我们模拟中的root,故我们在插入元素的时候需要从开始插入,所以为 ++ idx;
p = son[p][u];

最后我们的p指向的就是我们的结尾,我们这里去拿一个cnt数组去记录,代表以p为结尾的子串存在,代码为

cnt[p] ++;

AC代码

#include <cstdio>

using namespace std;

const int N = 100010;

char str[N];
int son[N][26], idx, cnt[N];           //为什么是son[N][26],因为我们只有小写字母

int insert(char str[])
{
    int p = 0;
    for (int i = 0; str[i]; i ++ )
    {
        int u = str[i] - 'a';
        if (!son[p][u]) son[p][u] = ++ idx;
        p = son[p][u];
    }
    cnt[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;
        p = son[p][u];
    }
    
    return cnt[p];
}

int main()
{
    char op[2];
    int n;
    scanf("%d", &n);
    
    while (n -- )
    {
        scanf("%s", op);
        if (op[0] == 'I')
        {
            scanf("%s", str);
            insert(str);
        }
        else 
        {
            scanf("%s", str);
            printf("%d\n", query(str));
        }
    }
    
    return 0;
}

2.AcWing 143. 最大异或对

本题链接:AcWing 143. 最大异或对
本博客给出本题截图:

在这里插入图片描述

关于本题

因为我们的节点只有值为1和0这两种情况,所以我们开的son数组的二维是2,
如何取异或后的最大值,从最高位开始,我们要每次尽量取不一样的,这里拿例题中的输入样例去举例:
1的二进制为01
2的二进制为10
3的二进制为11

我们从1开始,1的二进制最高位是0,所以我们要找到第一位是1的,发现2和3的二进制最高位的值恰好是1,所以继续看1的二进制第二位是1,为了让异或的值最大,我们需要找到父节点为1,子节点为0的值,2满足这个条件,所以和1异或后数最大的数是2,1^2 = 3,依次类推,如果我们在找子节点的时候发现没有满足条件的点,那么被迫无奈也只能顺着现有的子节点继续走下去.

AC代码

#include <cstdio>
#include <algorithm>

using namespace std;

const int N = 100010, M = 31 * N;     //一个数的最大位数是31,因为有N个数,所以开辟 M = 31 * N;

int a[N], son[M][2];
int n, idx;

void insert(int a)
{
    int p = 0;
    
    for (int i = 30; ~i; i -- )
    {
        int x = a >> i & 1;
        if (!son[p][x]) son[p][x] = ++ idx;
        p = son[p][x];
    }
}

int query(int a)
{
    int p = 0, res = 0;
    for (int i = 30; ~i; i -- )
    {
        int x = a >> i & 1;
        if (son[p][!x])
        {
            res = res * 2 + !x;
            p = son[p][!x];
        }
        else 
        {
            res = res * 2 + x;
            p = son[p][x];
        }
    }
    
    return res;
}

int main()
{
    scanf("%d", &n);
    
    for (int i = 0; i < n; i ++ ) 
    {
        scanf("%d", &a[i]);
        insert(a[i]);
    }
    
    int res = 0;
    
    for (int i = 0; i < n; i ++ ) 
        res = max (res, query(a[i]) ^ a[i]);
    
    printf("%d", res);
    
    return 0;
    
}

三、时间复杂度

关于Trie算法的时间复杂度以及证明,后续会给出详细的说明以及证明过程,目前先鸽了。

  • 13
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

辰chen

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值