Trie树(字典树)

含义:

Trie树一般指字典树,又称单词查找树,Trie树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高。

假设我们有五个字符串,code、cook、five、file、fat。现在需要在里面多次查找某个字符串是否存在。如果每次查找是拿查找的字符串和这5个字符串一次进行匹配,那么效率就会比较低。
如果将这五个字符串组织成字典树,就是下面这个图,查找起来会更快。
在这里插入图片描述
性质:

  • 根节点不包含字符,除根节点外每一个节点都只包含一个字符
  • 从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串
  • 每个节点的所有子节点包含的字符都不相同

基本操作:

查找、插入和删除,当然删除操作比较少见。

Trie树构造
在这里插入图片描述
Trie树的插入操作
在这里插入图片描述

Trie树的插入操作:是将单词的每个字母逐一插进Trie树中。插入前先看该字母对应的结点是否已经存在,若存在共享结点,则不用创建对应的结点,若不存在,就需要创建一个新结点。最后,我们在最后一个字母结点进行标记(即图中的黄色结点代表标记)。(比如插入cook后,要对k所在的结点进行标记,代表c->o->o->k这条路径上的所有结点可以组成一个单词cook)

Trie树的查询操作:比如我们查找code,可以将要查找的单词code分割成单个的字母c、o、d、e,然后从Trie树的根结点开始匹配,直到每个结点都存在(需在一条路径上)且最后一个结点被标记,才算查找成功。如图所示,绿色的路径就是在Trie树中匹配的路径。
在这里插入图片描述


835. Trie字符串统计
题目描述
维护一个字符串集合,支持两种操作:

“I x”向集合中插入一个字符串x;
“Q x”询问一个字符串在集合中出现了多少次。
共有N个操作,输入的字符串总长度不超过 10^5,字符串仅包含小写英文字母。

输入格式
第一行包含整数N,表示操作数。

接下来N行,每行包含一个操作指令,指令为”I x”或”Q x”中的一种。

输出格式
对于每个询问指令”Q x”,都要输出一个整数作为结果,表示x在集合中出现的次数。

每个结果占一行。

数据范围
1≤N≤2∗10^4

输入样例:
5
I abc
Q abc
Q ab
I ab
Q ab

输出样例:
1
0
1

#include<bits/stdc++.h>
using namespace std;

const int N=1e5+10;
int son[N][26],cnt[N],idx;
char str[N];
//son[N][26]:存的值是儿子结点对应的idx
//son[N][26]:一维下标是父节点的idx,二维下标是这个父节点的直接子节点的str[i]-'a'的值

//cnt[N]:以'abc'为例, 最后一个字符'c'对应的idx作为cnt的下标。 
//cnt[N]:存的值是该以该idx结尾的字符串的个数,即有几个'abc'字符串 

//idx代表当前字符的编号,根结点为0 

void insert(char str[]) //插入字符串 
{
	int p=0;
	for(int i=0;str[i];i++) //对这个单词的每个字母依次进行插入 
	{
		int u=str[i]-'a';  //将a~z转换成0~25 
		if(!son[p][u]) son[p][u]=++idx; //如果这个字母没有就添加一个,给它一个编号 
		p=son[p][u];       //继续检索 
	}
	cnt[p]++; //单词'abc'插入之后,将最后一个字符c的cnt++,代表以c结尾的单词多了一个 
}

int query(char str[]) //查询字符串出现的次数 
{
	int p=0;
	for(int i=0;str[i];i++)
	{
		int u=str[i]-'a'; //将a~z转换成0~25
		if(!son[p][u]) return 0; //有某个结点不存在直接结束 
		p=son[p][u];  //这个结点存在就继续往下走 
	}
	return cnt[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;
}

举个例子:对①~⑤个字符串依次进行插入,那么每个字符对应的idx就为图中绿色的数字。
这题建议自己画一个字典树,然后按照你画的字典树把代码顺一遍,这样会好理解些…我是这么看的
在这里插入图片描述


AcWing 143. 最大异或对
在这里插入图片描述

#include<iostream>
using namespace std;
const int N = 31*1e5 + 10;
int a[N], son[N][2], idx;

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])
        {
            res = res*2 + !u;
            p = son[p][!u];
        }
        else
        {
            res = res*2 + u;
            p = son[p][u];
        }
    }
    return res;
}

int main()
{
    int n;
    cin >> n;
    
    int res = 0;
    for(int i=0; i<n; i++) //对于每一个a[i]先插入,再查询 a[0~i-1] 的数
    {
        cin >> a[i];
        insert(a[i]); 
        int t = query(a[i]); // 查询 a[0~i-1] 的数
        res = max(res,t ^ a[i]);
    }
    cout << res;
}

HDU 1247 Hat’s Words

在这里插入图片描述

方法一:

#include<iostream>
#include<string.h>
using namespace std;

const int N = 5e4+20;
int son[N][30],cnt[N],idx;
char str[N];
char ch[N][110];

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]>=1;
}

int main()
{
    int b = 0;
    char l[110],r[110];
    while(cin >> ch[b])
    {
        insert(ch[b++]);
    }
    
    for(int i=0; i<b; i++)
    {
        int len = strlen(ch[i]);
        if(len<=1) continue;
        
        for(int j=1; j<len; j++)
        {
            strcpy(r,ch[i]+j); //右半部分
            strcpy(l,ch[i]);
            l[j] = '\0';       //左半部分
            
            if(query(l) && query(r))
            {
                cout<<ch[i]<<endl;
                break;
            }
        }
    }
}

方法二:

#include<iostream>
#include<map>
using namespace std;

int main()
{
    map<string,int> m;
    string x;
    while(cin >> x)
    {
        m[x] = 1;
    }
    
    for(map<string,int>::iterator it=m.begin(); it!=m.end(); it++)
    {
        string a = it->first;
        for(int i=1; i<a.size(); i++)
        {
            string a1(a,0,i);//从0开始的i个字符
            string a2(a,i);  //从i开始的所有字符
            if(m.find(a1) != m.end() && m.find(a2) != m.end())
            {
                cout<<a<<endl;
                break;
            }
        }
    }
}

借鉴: 可以看看下面这篇,写的很详细。
看动画轻松理解「Trie树」

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值