一、引言
最近赛事密集,正是刷题备战的好窗口:
- 第十八届蓝桥杯软件赛 (C/C++ 程序设计、Python 程序设计)报名已于 2026-10-08 10:00---2026-12-18 14:00 开启,省赛定在 2027-04-10 9:00--13:00 ,全国总决赛 2027-06-12;组委会还同步推出了"全国高校备赛季"活动(备赛手册、免费模拟赛、每日一题)。
- 中国电子学会 2026 年 12 月软件编程等级考试 (C/C++ 一级至十级、Python 一级至六级)报名 2026-09-23---12-02 16:00(即将截止) ,全国测试 12-10 、统考 12-12(C/C++ 8:30--10:30、Python 14:00--15:00)。
- 全国青少年信息素养大赛·算法应用主题赛初赛考点大纲(参照电子学会《青少年软件编程等级评价指南》)明确把"字符串"作为独立章节,复决赛还包含编程操作题。
字符串处理是这三场考试的公共高频考点。今天我们就讲字符串处理的"地基"结构------字典树(Trie,又称前缀树),用一道原创题把它吃透,并给出 C++ 与 Python 双版本。
二、题目 / 项目目标
原创题:校园智能单词本
学校要做一个背单词小程序,需要支持三种操作(所有单词仅由小写字母组成):
- 任务 A(插入):把一个个单词加入词典(可重复插入,重复则计数 +1)。
- 任务 B(精确查询):给定一个单词,判断它是否在词典中出现过。
- 任务 C(前缀统计 / 前缀列举):给定一个前缀字符串,统计(或列举)词典中以该前缀开头的单词数量。
示例:依次插入
apple、app、apply、bat、batch。
查询
app→ 在词典中(出现过 1 次)。前缀统计
app→ 3(apple、app、apply 均以app开头)。前缀列举
app→app、apple、apply。
用哈希表能做 A、B,但前缀类问题(C)它做不了------这正是字典树的用武之地。
三、核心考点
- 字符串多叉树建模:把每个单词的字符依次挂成一棵树,从根到某一节点路径上的字符拼起来就是前缀。
- 两种计数 :
cnt(以某节点结尾的单词数,用于精确匹配)与pass(经过某节点的单词数,用于前缀统计)。 - 字符到子节点下标的映射 :小写字母用
c - 'a'映射到 0--25;更通用的场景用map/ 字典存子节点。 - 时间复杂度 O(单词长度):每次插入 / 查询只沿树走一条长度为词长的链。
- 与哈希表、集合的取舍:字典树胜在天然支持"前缀",哈希表胜在平均 O(1) 但无前缀能力。
四、解法 / 拆解
4.1 思路
- 每个节点挂 26 个(或若干个)子节点指针,外加两个计数器。
- 插入 :从根出发,逐字符往下走,每经过一个节点
pass++;走到词尾节点cnt++。 - 精确查询 :沿词走,中途断链即返回
false;走到词尾看cnt > 0。 - 前缀统计 :沿前缀走,中途断链返回 0;走完返回该节点的
pass。 - 前缀列举(任务 C 进阶) :先走到前缀末端节点,再 DFS 回溯所有
cnt > 0的节点,沿途拼出单词。
4.2 C++ 实现(数组版,速度快、适合竞赛)
cpp
#include <iostream>
#include <string>
#include <vector>
using namespace std;
const int ALPH = 26;
struct TrieNode {
TrieNode* son[ALPH];
int cnt; // 以此节点结尾的单词个数(精确匹配)
int pass; // 经过此节点的单词个数(前缀统计)
TrieNode() : cnt(0), pass(0) {
for (int i = 0; i < ALPH; i++) son[i] = nullptr;
}
};
struct Trie {
TrieNode* root;
Trie() { root = new TrieNode(); }
// 任务A:插入单词(可重复)
void insert(const string& w) {
root->pass++; // 空串(根)也代表一个前缀,先 +1
TrieNode* p = root;
for (char c : w) {
int idx = c - 'a';
if (!p->son[idx]) p->son[idx] = new TrieNode();
p = p->son[idx];
p->pass++;
}
p->cnt++;
}
// 任务B:精确查询单词是否存在
bool search(const string& w) {
TrieNode* p = root;
for (char c : w) {
int idx = c - 'a';
if (!p->son[idx]) return false;
p = p->son[idx];
}
return p->cnt > 0;
}
// 任务C:统计以 pre 为前缀的单词数量
int prefixCount(const string& pre) {
TrieNode* p = root;
for (char c : pre) {
int idx = c - 'a';
if (!p->son[idx]) return 0;
p = p->son[idx];
}
return p->pass;
}
};
// 使用示例
int main() {
Trie t;
t.insert("apple"); t.insert("app"); t.insert("apply");
t.insert("bat"); t.insert("batch");
cout << boolalpha;
cout << t.search("app") << "\n"; // true
cout << t.search("ap") << "\n"; // false(app 是前缀,不是完整单词)
cout << t.prefixCount("app") << "\n"; // 3
cout << t.prefixCount("ba") << "\n"; // 2
return 0;
}
提示:
search("ap")返回false但prefixCount("ap")返回 3------这正是cnt与pass的区别,也是新手最容易踩的坑。
4.3 Python 实现(字典版,省空间、写法直观)
python
class TrieNode:
__slots__ = ('son', 'cnt', 'passcnt')
def __init__(self):
self.son = {} # 子节点:字符 -> TrieNode
self.cnt = 0 # 以此节点结尾的单词个数
self.passcnt = 0 # 经过此节点的单词个数
class Trie:
def __init__(self):
self.root = TrieNode()
# 任务A:插入单词(可重复)
def insert(self, w: str):
self.root.passcnt += 1 # 空串(根)也代表一个前缀,先 +1
p = self.root
for ch in w:
if ch not in p.son:
p.son[ch] = TrieNode()
p = p.son[ch]
p.passcnt += 1
p.cnt += 1
# 任务B:精确查询
def search(self, w: str) -> bool:
p = self.root
for ch in w:
if ch not in p.son:
return False
p = p.son[ch]
return p.cnt > 0
# 任务C:前缀统计
def prefix_count(self, pre: str) -> int:
p = self.root
for ch in pre:
if ch not in p.son:
return 0
p = p.son[ch]
return p.passcnt
# 任务C进阶:列举所有以 pre 为前缀的单词
def collect(self, pre: str):
p = self.root
for ch in pre:
if ch not in p.son:
return []
p = p.son[ch]
res = []
self._dfs(p, pre, res)
return res
def _dfs(self, node, path, res):
if node.cnt > 0:
res.append(path)
for ch, nxt in node.son.items():
self._dfs(nxt, path + ch, res)
# 使用示例
t = Trie()
for w in ["apple", "app", "apply", "bat", "batch"]:
t.insert(w)
print(t.search("app")) # True
print(t.search("ap")) # False
print(t.prefix_count("app")) # 3
print(t.collect("app")) # ['app', 'apple', 'apply']
五、时间 / 空间复杂度
| 操作 | 时间复杂度 | 说明 |
|---|---|---|
| 插入 insert | O(L) | L 为单词长度,只走一条链 |
| 精确查询 search | O(L) | 同上 |
| 前缀统计 prefixCount | O(L) | 同上 |
| 前缀列举 collect | O(L + K) | K 为匹配单词总长度(最坏遍历整棵子树) |
空间复杂度 :节点总数不超过 所有单词字符数之和,即 O(ΣL)。数组版每个节点固定 26 个指针(空间略大但访问极快),字典版按需开节点(稀疏时更省)。
六、七个易错点
- 字符映射范围 :
c - 'a'只适用小写字母。含大写、数字、中文时必须改用map/ 字典,或先统一tolower再插入,否则下标越界或漏匹配。 - cnt 与 pass 混淆 :
cnt只在词尾 节点 +1(回答"是否完整出现过");pass在每个经过 节点 +1(回答"前缀有多少")。search看cnt,prefixCount看pass,千万别看反。 - 前缀≠单词 :
prefixCount("ap")=3但search("ap")=false------"ap" 只是别人前缀,自己从没被插入。判题时看清要的是"前缀数量"还是"完整单词存在"。 - 空串 / 空前缀 :
prefixCount("")返回根节点的pass,即总单词数(含重复),这往往是题目的隐藏用例。 - 节点存储方式选型 :海量"短而密"的单词用数组
son[26]更快;海量"长而稀"或多语种字符用map/ 字典更省内存,但常数更大。 - 多测 / 多组数据未清空 :数组指针版多次使用要整树
delete重建(或加used标记重置),否则上一组的数据会串进来。Python 字典版直接新建Trie()即可。 - 指针空 / 递归深度 :C++ 访问子节点前必须判
nullptr;Python 用递归collect时,极端长单词可能触发递归深度限制,可改成显式栈(迭代)遍历。
七、进阶
- 01-Trie 与最大异或数对 :把每个数的二进制位当字符建 Trie,能 O(位数) 求"与某数异或最大"的数------本号此前位运算篇(10-26)已详细拆解。
- 最长公共前缀(多词版):多单词的最长公共前缀,等价于在 Trie 上从根往下走,直到出现"分叉"(某节点有两个及以上非空子节点)或"到达某个单词结尾"为止。
- 字典树排序 / 去重:按字典序对 Trie 做 DFS 遍历,天然得到有序、去重的单词列表,比先排序再去重更优雅。
- 与哈希表 / 集合对比 :哈希表平均 O(1) 查询但无法回答前缀类问题;Trie 是 O(L) 且天然支持前缀------凡是"找以 xxx 开头"的题,优先想 Trie。
- AC 自动机(多模式匹配) :本质就是在 Trie 上为每个节点补一条
fail指针,把多模式串匹配变成一次文本遍历------本号AC 自动机篇(10-05)有完整讲解,Trie 就是它的底座。 - 可持久化 Trie:给 Trie 的每个版本"快照"复用未修改分支,可用于"区间内异或最值"等离线 / 在线问题(对应可持久化线段树思想)。
八、小结与互动
字典树是字符串处理的"地基"结构:用 O(L) 的插入 / 查询,换来了哈希表给不了的前缀能力 。记住两个计数 cnt(结尾)与 pass(路过),就能稳稳拿下精确查询、前缀统计、前缀列举三连击。它在蓝桥杯字符串题、电子学会考级字符串章节、信息素养大赛算法应用赛项里都频繁出场,务必练熟。
动手练一练 :在上面的 Trie 加一个 delete(w)(删除单词,注意 cnt 与 pass 都要正确 −1,且可释放孤儿子树),再想想"统计词典里有多少个单词恰好是另一个单词的前缀"该怎么一句话算出来?欢迎在评论区贴出你的实现~
如果觉得有用,点个关注,后续继续拆解蓝桥杯 / 信息素养 / 电子学会考级的高频算法考点。
📚 免费少儿编程资料(夸克网盘领取)
以下资料来自夸克网盘分享,点击链接可直接保存;若需在 App 内打开,也可复制下方明文链接:
- 全国青少年信息素养大赛复赛集训题目Python&C++.docx
https://pan.quark.cn/s/93995d3cb150 - 2024信息素养-智能算法应用挑战赛-复赛初中组题目7月7日.pdf
https://pan.quark.cn/s/da97b5dbf75d - Python背记手册.pdf
https://pan.quark.cn/s/7568ae9ca92b - Python课程
https://pan.quark.cn/s/a94bf02d00c6 - 2024信息素养大赛图形化复赛集训题答案3-9
https://pan.quark.cn/s/6ccab7ec3cbc - 2025年03月份电子学会考级真题
https://pan.quark.cn/s/4403c4228912 - 2025全国青少年信息素养大赛赛项说明
https://pan.quark.cn/s/d9d0df4a9f29 - 青少儿信息素养大赛编程资料
https://pan.quark.cn/s/4ab6bd83be8a
资料持续更新,关注获取最新分享。