用代码详细讲解Trie数存储和查询字符串的过程

 代码详解:

#include <iostream>
using namespace std;

const int N = 2e4 + 10;
int son[N][26], cnt[N], idx;

void insert(char str[])
{
    int p = 0;
    for(int i = 0; str[i]; i++)
    {
        int u = str[i] - 'a';
        if(son[p][u] == 0) son[p][u] = ++idx;
        p = son[p][u];      //p = idx;
        //trie数中的根节点并不是真实存在的,但是所有存储的字符串的首元素都被存储在二维数组son[N][26]的首元素(一维数组)里面
        //既然需要存储的字符串的首元素都存储在二维数组里面的首元素(一维数组)中,那么在一开始存储时,首选的节点就必须是二维数组中首元素的节点,所以一开始p = 0
        //在存储过程中,如果发现没有这个节点,就会创建这个节点(son[p][u] = ++idx)
        //在创建完这个节点后,p的更改位此时节点的值,p的值始终和idx的值相等,而idx的值是一直++的,不会重复出现
        //那么p也不会出现重复,这样就能保证存储下一个节点时,是在另一个一维数组上
        //再然后是标记尾节点,存储完字符串,在末尾标进行一个标记
        //标记数组只要要是一维数组即可,因为结尾的节点的值等于p,p等于idx,idx不重复,所以每个节点都不会重复,那么只要一个一维数组就可以存储所有不重复的点
    }
    cnt[p]++;   //cnt[idx++];
}

int query(char str[])
{
    int p = 0;
    for(int i = 0; str[i] != '\0'; i++)
    {
        int u = str[i] - 'a';
        if(son[p][u] == 0) return 0;
        p = son[p][u];
        
        //查询也是从二维数组的首元素开始,因为所有字符串的首元素都存储在二维数组得首元素中,所以p = 0
        //接下来是判断当前节点是否等于0,如果等于0,就表明当前得节点还没有存储过元素,字符串也就不存在,返回0
        //否则一直判断,知道循环结束,那循环结束之后,就要返回当前cnt的p节点的值
        //如果这个p节点被标记了(cnt[p] != 0) 那就表明字符串是被存储在了trie树中        
    }
    return cnt[p];
}

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

代码详解:

​
#include <iostream>
using namespace std;

const int N = 1e5 + 10, M = 31 * N;
//输入的数据次数不超过1e5,而每个数的范围不超过2^31
//在字典树(Trie树)中,存储每个数是以二进制的形式存储的,每个节点存储这个数的二进制值
//那么以二进制的形式存储1e5个数,最多需要31*1e5个节点
int a[N], son[M][2], idx;
//字典树是一个二维数组,里面的元素是一维数组
//而一维数组的元素下标则是需要存储的字母或这二进制数的种类,所以这里只需要一个长度为2的一维数组即可
//这也从侧面说明当得知字母或者二进制后,我们会这样标记一个节点son[p][u];u是二进制或这字母的ASCII值

//关于插入操作:
void insert(int& x)
{
    int p = 0;
    for(int i = 30; i >= 0; i--)
    {
        int u = (x >> i) & 1;
        if(son[p][u] == 0) son[p][u] = ++idx;
        p = son[p][u];
        //关于这里的存储操作:
        //按照平时读数据的操作,从左往右读,所以依次求出x的第31位到第1位的二进制表示
        //接下来判断当前son[p][u]节点是否存在,如果不存在,就创建出这个节点
    }
}

//关于异或操作:
int search(int& x)
{
    int p = 0, res = 0;
    for(int i =  30; i >= 0; i--)
    {
        int u = (x >> i) & 1;
        if(son[p][!u] != 0)
        {
            res += 1 << i;
            p = son[p][!u];
            //查询操作:
            //对于x的某一位二进制,我们需要找到相反数才能让这一位异或得到1,使其最大,
            //但假如说这个相反的点不存在,那就只能找到标记点,继续下去
            //每次找到一个相反的数,就能在相应的位得到一个1,那么只需要让res加上1左移相应的位数即可
        }
        else p = son[p][u];
    }
    return res;
}


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

    for(int i = 0; i < n; i++) res = max(res, search(a[i]));
    printf("%d\n", res);
    return 0;
}

​

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值