详解Trie(c++)

文章介绍了Trie树(字典树)这种高效的数据结构,用于存储和查询字符串。通过Trie树,可以实现字符串插入和查询操作的时间复杂度为O(m),适合处理大量文本。文中提供了两个例子,一个是通过Trie树进行字符串集合的插入和计数查询,另一个是利用Trie树优化寻找两个整数异或运算的最大结果。
摘要由CSDN通过智能技术生成

Trie是一种高效的存储和查询字符串的数据结构

 

它主要用于字符串和文本的快速查找。Trie树是一种多叉树,它的每个节点都表示一个字符串的前缀,从根节点到叶子节点的路径组成了一个完整的字符串。Trie树的特点是时间复杂度为O(m),其中m为待查字符串的长度,与数据规模无关,因此它非常适合于海量文本的高效匹配。Trie的实现方式有很多种,比如基于数组、链表、指针和位运算等。

Trie是一种特殊的数据结构,用于字符串和文本的快速查找。

今天就看一下利用trie来完成两道题目更好的理解和应用

维护一个字符串集合,支持两种操作:

  1. I x 向集合中插入一个字符串 x;
  2. Q x 询问一个字符串在集合中出现了多少次。

共有 N 个操作,所有输入的字符串总长度不超过 10^5,字符串仅包含小写英文字母。

输入如下

5
I abc
Q abc
Q ab
I ab
Q ab

输出如下

1
0
1

这个时候就可以用trie来储存和查询字符串

那trie是怎么储存的呢,一开始,以首字母为公共祖宗的子节点,然后依次储存接下来的每一个字符,最后,在该字符串的最后打一个标记。

那为什么要打一个标记呢,其实是为了更好的接下来的查找

比如例题中,一开始储存了abc,然后在abc中c打一个标记,第二次查询ab这个字符的时候,直接看该字符串的最后是不是c,显然不是,所以可以直接不用从头开始查询,减少时间复杂度

为了大家更好地理解,给出下面这张图片

 那接下来要怎么查找呢,首先查询在trie树里面查找该字符串和打了标记的结点,有的话再从该字符串头结点开始查找,一直遍历,看有没有一条路是不是和该字符串相等

接下来在完成一开始的问题,具体代码如下

#include<iostream>
#include<algorithm>

using namespace std;

const int N=100010;

int son[N][26],cnt[N],idx;
char str[N];

void 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()
{
    int n;
    cin>>n;
    
    while(n--)
    {
        char op[2];
        scanf("%s%s",op,str);
        if(op[0]=='I') insert(str);
        else printf("%d\n",query(str));
    }
    
    return 0;
}

首先呢,该字符串只包含大小写字母,所以开一个son[N][26],后面的26是因为一开始最多只有26条路

cnt[N]记录的是最后一个节点出现的次数

idx是记录当前路线,如果没有而且要执行插入的时候,就多开辟一条路线并且记录

下面是另外一道题目

在给定的 N 个整数 A1,A2……AN中选出两个进行 xor(异或)运算,得到的结果最大是多少?

输入格式

第一行输入一个整数 N。

第二行输入 N 个整数 A1~AN

输入数据

3
1 2 3

输出数据

3

如果是两个数异或结束以后再与剩下的一个数开始,那样的时间复杂度太高了

所以现在我们就可以利用trie来做这道题目

上图如果要求与2异或最大的路径就是5,因为要最大值,肯定要与自身当前二进制的位数情况相反。

所以我们就可以先储存每个数的二进制路线,然后要找最大值的时候可以直接查找

代码如下

#include<iostream>

using namespace std;

const int N=100010,M=3000010;

int son[M][2],idx;
int q[N];

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

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

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

这个时候一开始就有两条路径,所以son数组后面的为2

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值