当前位置: 首页 > news >正文

算法题(194):字典树

审题:
本题需要我们对多组数据进行前缀查询,然后将查询到的数量打印出来

思路:
由于本题需要输出前缀查询书,故本题需要使用到字典树

补充数据结构:字典树

字典树是一种用于处理字符串相关问题的数据结构

用处:

1.查询字符串是否出现,出现过多少次

2.查询有多少个字符串是以字符串为前缀

假设我们需要将“abc”,"ab","abcd"“ac”放入字典树里面

图示:

我们用树的边来表示字符串的字符,一整条路径的字符放在一起就表示一个字符串

插入逻辑:从根节点开始,若存在该字符的边就继续往下走,如果不存在就开一个新的节点,并在边上存储新的字符

疑问:此时记录了“abcd”字符串和“ab”字符串,我们要如何表示出插入了“ab”?

因为“ab”的插入路径和“abcd”的路径是重叠的,所以我们需要用一个end数组负责记录结束在某个节点的字符串的个数

疑问:如果需要查看以某个字符串为前缀的字符串有多少个,如何维护?

我们需要使用pass数组负责记录该节点被经过了多少次,只需要读取该节点就知道以当前字符串为前缀的字符串有多少个


方法一:字典树

我们先将所有字符串都存储进字典树中,然后进行前缀字符串个数查询,最后输出结果

难点1:字符的映射如何映射?

因为本题的字符类型不仅有小写字母,还有大写字母和数字字符,所以我们不能只开辟26个位置给每一条边用。所有本题出现的字符类型是26+26+10.所以我们需要开辟62个位置。

然后将小写字母映射到0~25,大写字母映射到26~51,数字字符映射到52~61.

难点2:如何清空残留数据?

如果直接使用memset来清空数组数据会导致超时,因为总共的字符数可能有3e6,然后存储的字符类型又有62,如果全部遍历一次清空数据,那么他们乘起来的结果是1e8级别,会导致超时

那么我们其实可以只将使用过的tr数组数据清除,因为idx表示最后一个使用到的节点的索引,我们只要将idx个节点的数据清空即可

解题:

(1)变量创建
 

#include<iostream>
#include<cstring>
using namespace std;
const int N = 3e6 + 10;
int t,n, q;
int tr[N][62], p[N];
int idx;

tr数组:字典树,行的数量取决于所有字符串总共的字符个数,列的数量取决于需要存储进字典树的字符类型个数

行表示节点,列表示边的字符类型

p数组:用于记录各个节点被经过的次数,用于提取所需结果

idx变量:表示总共的节点数,可以用于进行时间复杂度较低的清空残留数据操作

(2)主函数逻辑

int main()
{cin >> t;while (t--){//清空残留数据for (int i = 0; i <= idx; i++){p[i] = 0;for (int j = 0; j < 62; j++ ){tr[i][j] = 0;}}idx = 0;cin >> n >> q;for (int i = 1; i <= n; i++)//数据录入字典树{string s;cin >> s;insert(s);}for (int i = 1; i <= q; i++)//进行字符前缀查询{string s;cin >> s;cout << find_prv(s) << endl;}}return 0;
}

(3)字典树构建

void insert(string& s)
{int cur = 0;p[cur]++;for (auto ch : s){int path = get_num(ch);if (tr[cur][path] == 0) tr[cur][path] = ++idx;cur = tr[cur][path];p[cur]++;}
}

构建的时候要注意将p数组维护好,只要表示经过该节点的时候就进行p[cur]++操作

逻辑:从根节点开始进行字符串插入

对字符串字符依次进行:

将对应字符转换成映射值后,判断当前是否已经创建了该字符的路径

若创建了就更新cur指针为该路径的下一个节点

若没有创建就创建一个新节点,即将该节点以path类型的边链接新的节点

(4)查找函数

int find_prv(string& s)
{int cur = 0;for (auto ch : s){int path = get_num(ch);if (tr[cur][path] == 0) return 0;cur = tr[cur][path];}return p[cur];
}

总体逻辑和插入差不多,只有几个区别

(1)途中不用改变p数组的值

(2)当发现路径无法进行下去(tr存储的值为0),直接返回0,表示没有该前缀

(3)找到后返回当前节点的p数组值,他表示经过该目标节点的次数

(5)映射函数

int get_num(char& c)
{if (c >= 'a' && c <= 'z'){return c - 'a';}else if (c >= 'A' && c <= 'Z'){return c - 'A' + 26;}else{return c - '0' + 52;}
}

P8306 【模板】字典树 - 洛谷

http://www.xdnf.cn/news/1396153.html

相关文章:

  • 从0到1玩转 Google SEO
  • Suno-API - OpenI
  • “FAQ + AI”智能助手全栈实现方案
  • Python从入门到高手9.4节-基于字典树的敏感词识别算法
  • 8月29日星期五今日早报简报微语报早读
  • 轮廓周长,面积,外接圆,外接矩形近似轮廓和模板匹配和argparse模块实现代码参数的动态配置
  • 【C++】掌握类模板:多参数实战技巧
  • 基于Net海洋生态环境保护系统的设计与实现(代码+数据库+LW)
  • MYSQL速通(2/5)
  • 小杰机器视觉(six)——模板匹配
  • UCIE Specification详解(十)
  • TypeScript: Symbol.iterator属性
  • WINTRUST!_GetMessage函数分析之CRYPT32!CryptSIPGetSignedDataMsg函数的作用是得到nt5inf.cat的信息
  • AI的“科学革命”:Karpathy吹响号角,从“经院哲学”走向“实验科学”
  • 基于STM32单片机的智能温室控制声光报警系统设计
  • Geocodify 的 API
  • CD71.【C++ Dev】二叉树的三种非递归遍历方式
  • 网络编程 反射【详解】 | Java 学习日志 | 第 15 天
  • 2025牛客暑期多校训练营4 G Ghost in the Parentheses 题解记录
  • Day17 Docker学习
  • uac播放与录制
  • 论文阅读:arixv 2025 WideSearch: Benchmarking Agentic Broad Info-Seeking
  • React Three Fiber
  • LBM——大型行为模型助力波士顿人形Atlas完成多任务灵巧操作:CLIP编码图像与语义,之后DiT去噪扩散生成动作
  • 编程速递:RAD Studio 13 即将到来的功能
  • Linux 线程调度核心要点
  • Shell 脚本基础教程
  • java序列化
  • Android系统框架知识系列(十九):Android安全架构深度剖析 - 从内核到应用的全栈防护
  • python学习打卡day48