蓝桥杯b组c++赛道---字典树

一. 字典树(Trie 树)基础概念

字典树是一种树形结构,用于高效地存储和检索字符串数据集 中的键,也叫前缀树。它的每个节点代表一个字符,从根节点到某一节点的路径上的字符连接起来,就形成了一个字符串前缀。比如存储字符串 "apple""app""banana","app" 这部分路径是共享的。

二. 字典树在 C++ 中的实现

以下是在 C++ 中实现字典树的关键部分:

定义数据结构

复制代码
const int N = 1e5 + 10;
int ch[N][26], cnt[N], idx; 
// ch表示树,ch[p][j] 表示存储从节点p沿j这条边走到下一子节点(j 取值 0 - 25 对应26个英文字母)
// 计数数组cnt[p] 存储以节点p结尾的单词的插入计数
// 节点编号idx,用来给新节点编号

插入字符串函数:

复制代码
void insert(char *s) {
    int p = 0;
    for (int i = 0; s[i]; i++) {
        int j = s[i] - 'a'; // 映射26位字母到0~25
        if (!ch[p][j]) ch[p][j] = ++idx; // 建立第一次在p节点遇见的字母的分支
        p = ch[p][j]; // 更新p到下一层
    }
    cnt[p]++; // 到达字符串末尾,该节点计数加1
}

这里从根节点开始,依次处理字符串的每个字符,若对应子节点不存在就创建,最后将字符串结束位置的节点计数加 1 。

查询函数

复制代码
int query(char *s) {
    int p = 0;
    for (int i = 0; s[i]; i++) {
        int j = s[i] - 'a';
        if (!ch[p][j]) return 0; // 没有找到对应分支,说明字符串没插入过
        p = ch[p][j];
    }
    return cnt[p]; // 返回以该字符串结尾的插入次数
}

该函数用于判断某个字符串在字典树中的出现次数是否大于 0 ,沿着字符串字符对应的分支走,若途中分支不存在则返回 0,走到末尾返回对应节点的计数。

三. 字典树在蓝桥杯 B 组 C++ 赛道的应用场景

字符串前缀匹配问题

比如题目中给出大量单词,让你统计有多少单词以某个特定前缀开头。通过字典树,插入所有单词后,用查询函数就能快速得到结果。例如有单词 "apple""app""apply",查询 "ap" 前缀,就能通过字典树高效得出有 3 个 。

字符串去重与计数

在插入字符串时,通过节点计数,可以统计每个字符串出现的次数,同时也能实现去重功能。比如统计一篇文章中每个单词出现的频率,就可以将单词依次插入字典树,最后看每个单词对应节点的计数 。

四、示例题目及分析:

复制代码
小蓝的神秘图书馆
问题描述
小蓝是图书馆的管理员,他负责管理图书馆的所有书籍。
图书馆有 N 本书,每本书都有名字,分别为 S1,S2,...,SN。
图书馆的读者们经常来询问小蓝,他们会给小蓝一个字符串T,
希望小蓝能告诉他们,图书馆里有多少本书的名字是以 T 的前缀开头的。
小蓝需要回答他们 M次 这样的询问。
现在,小蓝需要你的帮助。你能帮助小蓝解决这个问题,从而提升图书馆的服务质量书馆的服务质量吗?

解题思路

本题可使用字典树(Trie 树)来解决,以下是具体思路:

构建字典树
  1. 定义字典树的数据结构:
    • 用一个二维数组 son 来表示字典树的节点关系,son[i][j] 表示节点 i 的字符 j 对应的子节点(这里 j 可通过字符减去 'a' 映射到 0 - 25 的整数,用于表示 26 个英文字母)。
    • 用一个数组 cnt 记录每个节点被经过的次数,即有多少字符串以该节点为前缀。
    • 用一个变量 TOT 记录当前字典树中使用的节点总数。
  2. 插入操作:
    • 遍历每本书的名字字符串。从字典树的根节点(编号为 0 )开始,对于字符串中的每个字符,将其转换为 0 - 25 的索引值(u = S[i] - 'a' )。
    • 检查当前节点是否存在该字符对应的子节点,如果不存在则创建一个新节点(son[q][u] = ++TOT ),并将当前节点移动到该子节点(q = son[q][u] )。
    • 每经过一个节点,将该节点的计数加 1(cnt[q]++ ),表示有一个字符串经过了此节点。
查询操作
  1. 对于每个查询字符串 T ,同样从根节点开始。
  2. 遍历查询字符串的每个字符,将字符转换为索引值并沿着字典树的对应分支移动。
  3. 如果在移动过程中遇到某个字符对应的子节点不存在(son[q][u] == 0 ),说明不存在以该查询字符串为前缀的书籍名字,直接返回当前的计数(此时计数为 0 )。
  4. 如果顺利遍历完查询字符串,那么此时所在节点的 cnt 值就是以该查询字符串为前缀的书籍名字的数量,返回这个值。
整体流程
  1. 读取书籍数量 N 和查询次数 M
  2. 循环 N 次,读取每本书的名字并插入到字典树中。
  3. 循环 M 次,读取每个查询字符串,调用查询函数得到结果并输出
cpp 复制代码
#include<bits/stdc++.h>
using namespace std;

const int N=2e5+10;//N: 最大节点数的估计值
int n,q,cnt[N*27],son[N][27],TOT=0;
//n: 插入的字符串数量
//q: 查询的次数
//cnt[]: 记录每个节点被访问的次数(即有多少字符串以该节点为前缀)
//son[][]: 字典树的子节点数组,son[u][c] 表示节点 u 的字符 c 对应的子节点
//TOT: 当前使用的节点总数


//构造前缀树
void insert(string S) {
	int q=0;
	for(int i=0; i<S.size(); i++) {
		int u=S[i]-'a';//映射,把字符转为数字索引
		if(son[q][u] == 0)//检查当前节点 q 是否存在字符 u 对应的子节点。
			son[q][u]=++TOT;// 创建新节点并分配唯一编号。
		q=son[q][u];
		cnt[q]++;
	}
}

//匹配子串 
int query(string T){
	int q=0,ans=0;
	for(int i=0;i<T.size();i++){
		int u=T[i]-'a';
		q=son[q][u];
		if(q==0)return ans;//没找到
	}
	return cnt[q];
}

int main() {
	cin>>n>>q;
	string s;
	for(int i=1; i<=n; i++) {
		cin>>s,
		insert(s);
	}
	for(int i=1; i<=q; i++) {
		cin>>s,
		cout<<query(s)<<'\n';
	}

	return 0;
}
相关推荐
理智的灰太狼4 小时前
题目 3342: 蓝桥杯2025年第十六届省赛真题-红黑树
职场和发展·蓝桥杯
feiyangqingyun4 小时前
Qt/C++开发监控GB28181系统/sip协议/同时支持udp和tcp模式/底层协议解析
c++·qt·gb28181
我是李武涯7 小时前
C++ 条件变量虚假唤醒问题的解决
开发语言·c++·算法
岁忧8 小时前
(nice!!!)(LeetCode 每日一题) 3372. 连接两棵树后最大目标节点数目 I (贪心+深度优先搜索dfs)
java·c++·算法·leetcode·go·深度优先
一点.点10 小时前
针对C++开发工具推荐及分析(涵盖IDE、编译器、调试工具和辅助工具)
开发语言·c++·ide·开发工具
煤灰24210 小时前
C++的多态与继承
开发语言·c++
Echo``11 小时前
7:OpenCV—图像形态学处理
c++·图像处理·人工智能·opencv·计算机视觉
Tony小周11 小时前
QML与C++交互2
javascript·c++·交互
oioihoii12 小时前
C++23 <spanstream>:基于 std::span 的高效字符串流处理
c++·算法·c++23