字典树(Tire)及其拓展

一般树形结构都是需要指针来实现,但是在算法竞赛的过程中,使用传统指针的运行效率十分慢,因此我们通常使用一个数据idx来分配一个值来代替指针。

普通字典树

字典树也叫做前缀树,每个字符占据一个节点,拥有相同前缀的字符串可以共用部分节点
在这里插入图片描述
一般的字典树只有两种操作方式:插入和查询。

字典树要存放的一些信息

int t[1000010][26],cnt[1000010],idx;

idx就是指针,其中idx=0代表根节点(不存放任何信息)
t[N][26]:代表以当前指针值能够连接的字母的指针值。
cnt[N]:以当前指针值结尾的次数

普通字典树的插入操作

插入就是将该字符串放入字典树来,如果前缀在字典树中刚好存在,就按照当前指针继续往下找,如果不存在就新分配一个指针(++idx),如果要记录字符串出现的次数,那么就在当前指针的位置cnt++,代表以当前指针值结尾的次数++

void insert(char *str){
	//str就是传入的字符串
    int p=0;//本位指针,当前位置是根节点
    for(int i=0;str[i];i++){
        int u=str[i]-'a';//当前指针的位置下一个要连接的字母
        if(!t[p][u]) t[p][u]=++idx;//如果不存在就分配一个指针
        p=t[p][u];//本位指针的转移
    }
    cnt[p]++;//以当前位指针为结尾的次数
}

普通字典树的查询操作

查询当然还是要根据指针来查询的,如果我现在查到的位数,没有连接我要的下一位字母,代表现在在这棵字典树里面没有找到我们要求找到的字符串,如果全部找完了,返回一个cnt代表当前这棵字典树有几个这样的字符串。

int query(char *str){
    int p=0;
    for(int i=0;str[i];i++){
        int u=str[i]-'a';
        if(!t[p][u])return 0;
        else p=t[p][u];
    }
    return cnt[p];
}

例题


acwing 835. Trie字符串统计

在这里插入图片描述

#include<stdio.h>
#include<iostream>
using namespace std;
int t[1000010][26],cnt[1000010],idx;
void insert(char *str){
    int p=0;
    for(int i=0;str[i];i++){
        int u=str[i]-'a';
        if(!t[p][u]) t[p][u]=++idx;
        p=t[p][u];
    }
    cnt[p]++;
}
int query(char *str){
    int p=0;
    for(int i=0;str[i];i++){
        int u=str[i]-'a';
        if(!t[p][u])return 0;
        else p=t[p][u];
    }
    return cnt[p];
}
int main(){
	char qus,str[100010];
	int n;
	scanf("%d",&n);
	while(n--){
		getchar();
		scanf("%c %s",&qus,str);
		if(qus=='I')insert(str);
		else {
			if(!query(str)) printf("0\n");
			else printf("%d\n",query(str));
		}
	}return 0;
}

其他练习

洛谷P2580 于是他错误的点名开始了

01字典树

顾名思义,01字典树没有普通字典树的一些问题,它的字符不像普通字典树都是字符,01树只有0和1,主要用来解决一些数字问题。
其实理解了普通的字典树,那么这样的字典树就非常的容易。
如果我们要求两个数的最小异或值,那么就保证每一位(异或:相同为0不同为1)都不一样,那么根据字典树来找和现在的数都不一样的路径的数(尽量)。
01树一定是一个二叉树,每一条路径长度一定相同,长度是按照数的位数决定的。int是31,long long是63

例题

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

#include<iostream>
#include<cstring>
#include<queue>
#include<stack>
using namespace std;
int a[2102100];
int tr[2102100][2];
int idx;
void insert(int x){
	int p=0;
	for(int i=30;i>=0;i--){
		int &s=tr[p][x>>i&1];
		//引用,懒得写那么多了
		//x>>i&1的意思是第i+1位是1整个式子就是1
		//是0整个式子就是0
		if(!s)s=++idx;
		p=s;
	}
	return;
}
int query(int x){
	int p=0,ans=0;
	for(int i=30;i>=0;i--){
		int s=x>>i&1;
		if(tr[p][!s]){
		//尽量找到和当前不一样的路径组成的数
			p=tr[p][!s];
			ans+=1<<i;
		}
		else p=tr[p][s];
	}
	return ans;
}
int main(){
	int n;
	cin>>n;
	for(int i=1;i<=n;i++)cin>>a[i],insert(a[i]);
	//让所有的数建树(组成一棵01字典树)
	int ans=0;
	for(int i=1;i<=n;i++){
		ans=max(ans,query(a[i]));
		//记录最大值
	}
	cout<<ans<<endl;
}

可持久化字典树

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值