字典树Trie 简介和相关例题分析

一.字典树定义

概念:字典树(TrieTree),是一种树形结构,典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串,如01字典树)。主要思想是利用字符串的公共前缀来节约存储空间。很好地利用了串的公共前缀,节约了存储空间。字典树主要包含两种操作,插入查找

字典树,是一种哈希树的变种。是一种用于快速查询某个字符串/字符前缀是否存在的数据结构。它的优点是:最大限度地减少无谓的字符串比较,查询效率比哈希表高。核心思想是空间换时间。利用字符串的公共前缀来降低查询时间的开销以达到提高效率的目的

二. 字典树的实现

在用代码实现字典树时,我们主要需要实现两种操作:即插入单词和查找单词,这里我们用两个函数来实现,然后我们用一个二维数组来实现字典树的建树。

在讲解两个函数之前,我们先来了解一下其中需要用到的工具

1.变量id: id代表字典树中每一个节点的编号,id的大小只与插入字典树的先后顺序有关,它的作用在下面会讲到。

2.trie[N][26]: 每个trie代表一条边,字典树其中1~N为边上方节点的编号,0代表root节点,1~26为连在i节点下方的26个字母。如果trie[i][x]=0,则代表字典树中目前没有这个点,而trie[i][x]的值代表这个点下方连有的点的编号,例如:trie[i][2]=9代表第i号点和的下方连有一个点‘c’,并且那个点的编号是9,为什么是c呢?因为 ‘c’-‘a’=2

3.cnt[N]: cnt[i]==0代表编号为i的点不是一个单词的结束点,在上面的图中代表这个点不是空点,但是没有标红,cnt[i]!=0代表编号为i的点是一个单词的结束点,即红点。cnt[i]不一定只为0或1,因为有可能多次输入了同一个单词。

4.(难点)两个函数中的变量p:
p代表查询与插入时的不断变化的当前节点编号,初始化为0,代表初始节点,在函数的循环中,我们首先用x确定接下来要找的字母,再通过变量x确定了接下来我们需要查找当前节点下是否有连接着目标字母的节点。通过每次确定的x,我们通过trie[p][x] 查找连着目标字母的节点的编号,如果目标节点存在,就把p更新成目标节点的编号(p = trie[p][x])。而如果trie[p][x] == 0,代表字典树中没有这个点,如果是查找就代表没有这个单词,查找失败。而如果是插入函数,我们就用 ++id 来把这个点存进字典树。我们在两个函数的最后用cnt[p]来涂红节点或返回节点值。

原文出处:https://blog.csdn.net/qq_49688477/article/details/118879270

一.插入函数的实现

插入函数其实就是一个建树的过程,我们通过不断的插入字符串,不断的建成一颗字典树,那我们该如何建树呢?

void insert(char s[])
{
	int p = 0;        //p指针一开始为0,即在根节点位置
	for (int i = 0; s[i]; i++) {
		int q = s[i] - 'a' + 1;    //将字符转换成整型数值,方便存储
		if (!f[p][q]) f[p][q] = ++id;  //如果没有该节点,就新建一个
		p = f[p][q];      //进行往下建树
	}
	cnt[p]++;  //插入次数
}

如果我们依依次插入“cat" “car" “busy" “cate" “bus" “car'’ 这几个字符串,我们可以得到这样一颗字典树。

二.查找函数的实现

既然我们知道如何插入,建成一颗字典树了,那么我们查找只要在插入的基础上进行就可以,我们从根节点开始进行查询,类似于前序遍历一样,进行查找。

int find(char s[])
{
	int p = 0;   //p指针指向根节点
	for (int i = 0; s[i]; i++) {
		int q = s[i] - 'a' + 1;
		if (!f[p][q]) return 0;   //如果没有找到对应的字符,退出
		p = f[p][q];
	}               //如果循环正常结束,说明已经找到
	return cnt[p];  //返回插入的次数
}

三.相关例题分析

一.P8306 【模板】字典树

题目描述

给定 n 个模式串 s1​,s2​,…,sn​ 和 q 次询问,每次询问给定一个文本串 ti​,请回答 1​∼sn​ 中有多少个字符串 sj​ 满足 ti​ 是 sj​ 的前缀

一个字符串 t 是 s 的前缀当且仅当从 s 的末尾删去若干个(可以为 0 个)连续的字符后与 t 相同。

输入的字符串大小敏感。例如,字符串 Fusu 和字符串 fusu 不同。

输入格式

本题单测试点内有多组测试数据

输入的第一行是一个整数,表示数据组数 T。

对于每组数据,格式如下:
第一行是两个整数,分别表示模式串的个数 n 和询问的个数 q。
接下来 n 行,每行一个字符串,表示一个模式串。
接下来 q 行,每行一个字符串,表示一次询问。

输出格式

按照输入的顺序依次输出各测试数据的答案。
对于每次询问,输出一行一个整数表示答案。

输入输出样例

输入 #1复制

3
3 3
fusufusu
fusu
anguei
fusu
anguei
kkksc
5 2
fusu
Fusu
AFakeFusu
afakefusu
fusuisnotfake
Fusu
fusu
1 1
998244353
9

输出 #1复制

2
1
0
1
2
1

说明/提示

数据规模与约定

对于全部的测试点,保证1≤T,n,q≤105,且输入字符串的总长度不超过3×106。输入的字符串只含大小写字母和数字,且不含空串。

说明

std 的 IO 使用的是关闭同步后的 cin/cout,本题不卡常。

这是一道字典树模版题,只要把我们刚刚介绍的东西弄懂了,就OK了,我们就通过这道题来理解一下字典树例题的解题思路(注意这道题,这里有多组数据,要清空数组)

#include<bits/stdc++.h>
using namespace std;
#define N 3000005
int s[N][126], cnt[N];   //s数组用来建数
int t, q, n, p, sum, id;
char str[N];

void clear()            //清空操作,因为有多组数据
{
	for (int i = 0; i <= id; i++) {
		cnt[i] = 0;
		for (int j = 0; j <=126; j++) {
			s[i][j] = 0;
		}
	}
	id = 0;
}

void insert(char str[])   //插入函数
{
	int len = strlen(str);  
	p = 0;
	for (int i = 0; i < len; i++) {
		int j = (int)str[i];   //转换为整型
		if (!s[p][j])          //如果没有该节点,新建一个
			s[p][j] = ++id;
		p = s[p][j];      //继续进行往下插入
		cnt[p]++;       //记录插入次数
	}
}

int find(char str[])       //查找操作
{
	int len = strlen(str);
	p = 0;
	for (int i = 0; i < len; i++) {
		int j = (int)str[i];
		if (!s[p][j])      //如果没有找到对应字符,退出
			return 0;
		p = s[p][j];
	}
	return cnt[p];     //返回插入次数
}

int main()
{
	cin >> t;
	while (t--) {
		clear();      //一定要清空数组
		cin >> n >> q;
		for (int i = 1; i <= n; i++) {
			cin >> str;
			insert(str);
		}
		for (int i = 1; i <= q; i++) {
			cin >> str;
			cout << find(str) << endl;
		}
	}
	return 0;
}


二.P2580 于是他错误的点名开始了

题目背景

XS中学化学竞赛组教练是一个酷爱炉石的人。

他会一边搓炉石一边点名以至于有一天他连续点到了某个同学两次,然后正好被路过的校长发现了然后就是一顿欧拉欧拉欧拉(详情请见已结束比赛 CON900)。

题目描述

这之后校长任命你为特派探员,每天记录他的点名。校长会提供化学竞赛学生的人数和名单,而你需要告诉校长他有没有点错名。(为什么不直接不让他玩炉石。)

输入格式

第一行一个整数 n,表示班上人数。

接下来 n 行,每行一个字符串表示其名字(互不相同,且只含小写字母,长度不超过 5050)。

第 n+2 行一个整数 m,表示教练报的名字个数。

接下来 m 行,每行一个字符串表示教练报的名字(只含小写字母,且长度不超过 5050)。

输出格式

对于每个教练报的名字,输出一行。

如果该名字正确且是第一次出现,输出 OK,如果该名字错误,输出 WRONG,如果该名字正确但不是第一次出现,输出 REPEAT

输入输出样例

输入 #1复制

5  
a
b
c
ad
acd
3
a
a
e

输出 #1复制

OK
REPEAT
WRONG

说明/提示

  • 对于 40% 的数据,n≤1000,m≤2000。
  • 对于 70% 的数据,n≤104,m≤2×104。
  • 对于 100% 的数据,n≤104,m≤105。

这道题也类似于模版题,只要我们熟悉插入和查找的过程,一样可以解决,这里只要注意一下第一次出现和其它次出现所输出是不一样的,这里我们只要在查找函数中返回不同的值,这样就可以解决了。

#include<bits/stdc++.h>
using namespace std;
#define N 1000005
int n, m, id, ans, num;
int f[N][27], cnt[N];   //f数组为建树用,cnt数组记录插入次数
bool vis[N];    //标记数组
char s[N];

void insert(char s[])   //插入函数
{
	int p = 0;  //p指针指向根节点
	for (int i = 0; s[i]; i++) {
		int q = s[i] - 'a' + 1;
		if (!f[p][q]) f[p][q] = ++id;  //如果没有该节点,就新建一个
		p = f[p][q];    //继续往下插入
	}
	vis[p] = true;    //插入完成,为下面查询时做铺垫
}

int find(char s[])    //查找函数,注意这里要返回0,1,2三个值代表不同状态
{
	int p = 0;
	for (int i = 0; s[i]; i++) {
		int q = s[i] - 'a' + 1;
		if (!f[p][q]) return 0;   //如果没有查找到该字符,退出
		p = f[p][q];
	}
	if (!vis[p]) return 0;   //没有查找到该字符串
	if (cnt[p]==0) {         //找到该字符串,但要讨论是第几次查到
		++cnt[p];
		return 1;
	}
	return 2;
}
int main()
{
	memset(vis, false, sizeof(vis));
	memset(cnt, 0, sizeof(cnt));
	cin >> n;
	for (int i = 1; i <= n; i++) {
		cin >> s;
		insert(s);
	}
	cin >> m;
	for (int i = 1; i <= m; i++) {
		cin >> s;
		int k = find(s);
		if (k == 0)                   //如果没有找到
			cout << "WRONG" << endl;
		if (k == 1)                   //如果第一次找到
			cout << "OK" << endl;
		if (k == 2)                   //如果不是第一次找到
			cout << "REPEAT" << endl;
	}
	return 0;
}

OK,本次关于字典树的总结就结束了,如果对于本篇总结有疑问的欢迎讨论,同时如果有错误或者待修改完善的地方,也希望能够指出,我一定会及时改正,~QVQ~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/397822.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【运维】站点可靠性工程介绍:研发,运维,SRE,Devops的关系

文章目录 1、什么是SRE2、SRE与研发、运维的区别 1、什么是SRE 站点可靠性工程&#xff08;SRE&#xff09; 是 IT 运维的软件工程方案。 SRE 团队使用软件作为工具&#xff0c;来管理系统、解决问题并实现运维任务自动化。 SRE 执行的任务以前通常由运维团队手动执行&#x…

网络运行安全

网络运行安全 第一节 一般规定 第二十一条 国家实行网络安全等级保护制度。网络运营者应当按照网络安全等级保护制度的要求,履行下列安全保护义务,保障网络免受干扰、破坏或者未收授权的访问,防止网络数据泄露或者被窃取、篡改: 制定内部安全管理制度和操作规程,确定网络…

深度学习图像算法工程师--面试准备(1)

1 请问人工神经网络中为什么 ReLU 要好过于 tanh 和 Sigmoid function&#xff1f; 采⽤Sigmoid 等函数&#xff0c;算激活函数时&#xff08;指数运算&#xff09;&#xff0c;计算量⼤&#xff0c;反向传播求误差梯度时&#xff0c;求导涉及除法和指数运算&#xff0c;计算量…

【常识】大数据设计基础知识

底层存储&#xff1a;hadoop&#xff08;hdfsmapreduce&#xff09; Hadoop已经有十几年的历史&#xff0c;它是大数据领域的存储基石&#xff0c;HDFS目前仍然没有成熟替代品;MapR 文件系统在业内已经具有一定知名度了&#xff0c;不仅 MapR 宣布它自己的文件系统比 HDFS 快2-…

十三、集合进阶——单列集合 及 数据结构

单列集合 及 数据结构 13.1 集合体系结构13.1.2 单列集合1. Collection2.Collection 的遍历方式迭代器遍历增强for遍历Lambda表达式遍历 3.List集合List集合的特有方法List集合的遍历方式五种遍历方式对比 4.数据结构1).栈2).队列3&#xff09;数组4&#xff09;链表小结5&…

嵌入式学习-qt-Day1

嵌入式学习-qt-Day1 一、思维导图 二、作业 1.自由发挥登录窗口的应用场景&#xff0c;实现一个登录窗口界面 #include "widget.h"Widget::Widget(QWidget *parent): QWidget(parent) {//字体设置QFont font1;//创建字体对象1font1.setWeight(QFont::Bold);//字体…

普法:正当防卫,保护自己

今天该换一换口味了&#xff0c;所以本“人民体验官”推广人民日报官方微博《警察小哥科普第二十条指什么》。 图&#xff1a;来源“人民体验官”推广平台 电影《第二十条》片名&#xff0c;取自刑法第二十条规定。这一法条具体写了什么&#xff1f;对我们的生活有何影响&…

《白话C++》第10章 STL和boost,Page105 enable_shared_from_this

说到“循环引用”&#xff0c;其中“自己对自己”的引用是最直接的循环引用&#xff0c;如图10-12所示。 而说到“自己”&#xff0c;在C语言中应该首先想到的类的“this”指针。不过&#xff0c;this指针是裸指针&#xff0c;如果我们在类中&#xff0c;需要传递当前对象本身&…

【嵌入式-Keil】keil代码提示快捷键

CTRL空格 如果没有提示&#xff0c;可能跟输入法的快捷键冲突&#xff0c; 右键->设置->按键->勾掉第一个就行了 再按CTRL空格就有提示了 参考&#xff1a;串口发送&串口发送接收

Vue | (三)使用Vue脚手架(中)| 尚硅谷Vue2.0+Vue3.0全套教程

文章目录 &#x1f4da;Todo-list 案例&#x1f407;组件化编码流程&#xff08;通用&#xff09;&#x1f407;实现静态组件&#x1f407;展示动态数据&#x1f407;交互⭐️添加一个todo⭐️todo勾选实现⭐️删除功能实现⭐️底部统计功能实现⭐️底部全选功能实现⭐️底部一…

【黑马程序员】C++文件操作

20240220 文章目录 文件操作背景文件分类操作文件的三大类 文本文件写文件写文件步骤文件打开方式代码示例 读文件读文件步骤代码示例 写二进制文件写二进制文件步骤代码示例 读二进制文件代码示例 文件操作 背景 程序运行时产生的数据都属于临时数据&#xff0c;程序一旦运行…

TypeScript(三):TypeScript面向对象

TypeScript面向对象 类的定义 与JS不同的是&#xff0c;成员属性需要在前面进行提前声明 class Person{//需要在前面对成员变量进行声明name: string//声明的时候&#xff0c;可以对值进行初始化&#xff0c;初始化可以带有类型注解&#xff0c;也可以省略age 18//construc…

基于YOLOv7算法和Widerperson数据集的高精度实时行人检测系统(PyTorch+Pyside6+YOLOv7)

摘要&#xff1a;基于YOLOv7算法和Widerperson数据集的高精度实时行人检测系统可用于日常生活中检测与定位行人目标&#xff0c;此系统可完成对输入图片、视频、文件夹以及摄像头方式的目标检测与识别&#xff0c;同时本系统还支持检测结果可视化与导出。本系统采用YOLOv7目标检…

3个密码学相关的问题

一、离散对数问题&#xff08;Discrete Logarithm Problem, DLP&#xff09; 问题描述&#xff1a;给定 有限阿贝尓群 G中的2个元素a和b&#xff0c;找出最小的正整数x满足&#xff1a;b a ^^ x &#xff08;或者证明这样的x不存在&#xff09;。 二、阶数问题&#xff08;O…

云服务器ECS价格表出炉——阿里云

2024年阿里云服务器租用价格表更新&#xff0c;云服务器ECS经济型e实例2核2G、3M固定带宽99元一年、ECS u1实例2核4G、5M固定带宽、80G ESSD Entry盘优惠价格199元一年&#xff0c;轻量应用服务器2核2G3M带宽轻量服务器一年61元、2核4G4M带宽轻量服务器一年165元12个月、2核4G服…

[element] el-upload实现 “读取本地表格内容并上传“

需求: 通过表格一键导入数据 表格模板: 导入按钮: <el-uploadref"upload"class"filter-item"style"margin-left: 10px"action"/"accept".csv, application/vnd.ms-excel, application/vnd.openxmlformats-officedocument.sp…

Open3D三维重建

原始点云&#xff1a; alpha_shape算法 import open3d as o3dpcd o3d.io.read_point_cloud("airplane_0001.pcd") mesh o3d.geometry.TriangleMesh.create_from_point_cloud_alpha_shape(pcd, alpha0.1) o3d.visualization.draw_geometries([mesh], mesh_show_b…

相机图像质量研究(39)常见问题总结:编解码对成像的影响--运动模糊

系列文章目录 相机图像质量研究(1)Camera成像流程介绍 相机图像质量研究(2)ISP专用平台调优介绍 相机图像质量研究(3)图像质量测试介绍 相机图像质量研究(4)常见问题总结&#xff1a;光学结构对成像的影响--焦距 相机图像质量研究(5)常见问题总结&#xff1a;光学结构对成…

我把ChatGPT部署到我的手机上

正常的大模型部署都是在服务器上的 但是最近我看到一个手机上可以运行的大模型 分享给大家 MiniCPM MiniCPM是基于 MLC-LLM 开发&#xff0c;将 MiniCPM 和 MiniCPM-V 在 Android 手机端上运行。 使用起来很简单&#xff0c;下载好安装包后 按照教程安装好 下载2个模型 一个是M…

C++拷贝构造函数与赋值运算符重载

顾得泉&#xff1a;个人主页 个人专栏&#xff1a;《Linux操作系统》 《C从入门到精通》 《LeedCode刷题》 键盘敲烂&#xff0c;年薪百万&#xff01; 一、拷贝构造函数 1.概念 在现实生活中&#xff0c;可能存在一个与你一样的自己&#xff0c;我们称其为双胞胎。 那在创…