news 2026/7/24 15:28:23

【字典树 C++ 实现】

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【字典树 C++ 实现】

文章目录

  • 前言
  • 一、字典树(Trie)是什么?
  • 二、基本操作与算法思路
    • 1. 插入(Insert)
    • 2. 查找(Search)
    • 3. 前缀判断(StartsWith)
    • 4. 删除(Remove)
    • 5. 自动补全(Autocomplete)
  • 三、C++ 实现
  • 四、测试
  • 五、复杂度分析

前言

字典树(Trie,也叫前缀树)适合用于实现自动补全、前缀搜索、单词字典、敏感词过滤等功能。


一、字典树(Trie)是什么?

Trie 是一棵多叉树(每个结点代表一个字符),从根节点到某个结点的路径表示一个字符串的前缀或整个单词。常见特征:

  • 根节点代表空字符串。
  • 每个边对应一个字符(例如英语小写字母 a–z),结点可以有多个子结点。
  • 结点通常保存“是否是单词结尾”的标记(isEnd)。
  • 查询单词或判断前缀都可以在树上沿字符逐层访问完成,时间复杂度与单词长度成线性关系。

优点:查询、插入、前缀查询时间优秀(O(L)),适合海量字符串前缀操作。缺点:内存消耗可能较大(尤其当字母表大或字符串稀疏时)。


二、基本操作与算法思路

1. 插入(Insert)

从根开始,对单词的每个字符:

  • 若当前结点没有对应子结点则创建。
  • 移动到子结点,处理下一个字符。
    最后标记当前结点为单词结尾。

时间复杂度:对单词长度为 L,插入为 O(L)。

2. 查找(Search)

类似插入,不创建结点,只沿字符查找:

  • 若任一字符对应子结点缺失,则单词不存在。
  • 如果遍历完字符且当前结点 isEnd 为真,则单词存在。

时间复杂度:O(L)。

3. 前缀判断(StartsWith)

沿字符查找,若能走完前缀字符则存在前缀。时间复杂度:O§,P 为前缀长度。

4. 删除(Remove)

删除相对复杂,要保证只删除不再被任何单词使用的结点。常用办法是递归:

  • 递归到单词末尾,取消 isEnd 标记。
  • 如果该结点没有子结点,则返回 true 表示该结点可删除,父结点将其指针置空并继续判断。
  • 否则不可删除,返回 false。

时间复杂度:O(L)。

5. 自动补全(Autocomplete)

先定位到前缀结点,然后对该子树做 DFS/BFS 收集最多 k 个单词或全量单词。时间复杂度:查找前缀 O§ + 遍历匹配单词的复杂度(取决于输出量与深度)。


三、C++ 实现

#include<bits/stdc++.h>usingnamespacestd;/* Trie 实现(26 小写字母) 提供:insert, search, startsWith, remove, autocomplete */classTrieNode{public:boolisEnd;// 子结点指针数组(26)array<TrieNode*,26>children;TrieNode():isEnd(false){children.fill(nullptr);}~TrieNode(){for(autop:children){if(p)deletep;}}};classTrie{private:TrieNode*root;// 删除单词的递归函数,返回是否可以删除当前节点boolremoveHelper(TrieNode*node,conststring&word,intdepth){if(!node)returnfalse;if(depth==(int)word.size()){if(!node->isEnd)returnfalse;// 单词不存在node->isEnd=false;// 如果没有子节点,则可以删除该节点for(autoch:node->children)if(ch)returnfalse;returntrue;}intidx=word[depth]-'a';TrieNode*child=node->children[idx];if(!child)returnfalse;boolshouldDeleteChild=removeHelper(child,word,depth+1);if(shouldDeleteChild){deletechild;node->children[idx]=nullptr;// 判断当前节点是否能被删除:非单词结尾且无任何子节点if(!node->isEnd){for(autoch:node->children)if(ch)returnfalse;returntrue;}else{returnfalse;}}returnfalse;}// 自动补全:从 node 开始 DFS 收集单词voiddfsCollect(TrieNode*node,string&path,vector<string>&out,intlimit){if(!node)return;if((int)out.size()>=limit)return;if(node->isEnd)out.push_back(path);for(inti=0;i<26&&(int)out.size()<limit;++i){if(node->children[i]){path.push_back('a'+i);dfsCollect(node->children[i],path,out,limit);path.pop_back();}}}public:Trie(){root=newTrieNode();}~Trie(){deleteroot;}voidinsert(conststring&word){TrieNode*cur=root;for(charc:word){intidx=c-'a';if(idx<0||idx>=26){// 简化:本实现只支持小写字母,遇到其他字符可以选择跳过或抛错continue;}if(!cur->children[idx])cur->children[idx]=newTrieNode();cur=cur->children[idx];}cur->isEnd=true;}boolsearch(conststring&word)const{TrieNode*cur=root;for(charc:word){intidx=c-'a';if(idx<0||idx>=26)returnfalse;if(!cur->children[idx])returnfalse;cur=cur->children[idx];}returncur->isEnd;}boolstartsWith(conststring&prefix)const{TrieNode*cur=root;for(charc:prefix){intidx=c-'a';if(idx<0||idx>=26)returnfalse;if(!cur->children[idx])returnfalse;cur=cur->children[idx];}returntrue;}voidremove(conststring&word){removeHelper(root,word,0);}// 返回最多 limit 个以 prefix 为前缀的单词(按字典序)vector<string>autocomplete(conststring&prefix,intlimit=10){vector<string>res;TrieNode*cur=root;for(charc:prefix){intidx=c-'a';if(idx<0||idx>=26)returnres;if(!cur->children[idx])returnres;cur=cur->children[idx];}string path=prefix;dfsCollect(cur,path,res,limit);returnres;}};

四、测试

intmain(){Trie trie;vector<string>words={"apple","app","application","apt","banana","band","bandit","bat"};for(auto&w:words)trie.insert(w);// 测试 searchcout<<boolalpha;cout<<"search(\"app\") = "<<trie.search("app")<<"\n";// truecout<<"search(\"apply\") = "<<trie.search("apply")<<"\n";// false// 测试 startsWithcout<<"startsWith(\"ap\") = "<<trie.startsWith("ap")<<"\n";// truecout<<"startsWith(\"ba\") = "<<trie.startsWith("ba")<<"\n";// truecout<<"startsWith(\"cat\") = "<<trie.startsWith("cat")<<"\n";// false// 自动补全autocands=trie.autocomplete("ap",5);cout<<"autocomplete(\"ap\"):\n";for(auto&s:cands)cout<<" "<<s<<"\n";// 删除trie.remove("app");cout<<"after remove(\"app\") search(\"app\") = "<<trie.search("app")<<"\n";// depends: app was word, now falsecout<<"after remove(\"app\") startsWith(\"app\") = "<<trie.startsWith("app")<<"\n";// true (because application, apple)// 删除 "application" 再测试trie.remove("application");cout<<"after remove(\"application\") startsWith(\"app\") = "<<trie.startsWith("app")<<"\n";// still true because "apple"trie.remove("apple");cout<<"after remove(\"apple\") startsWith(\"app\") = "<<trie.startsWith("app")<<"\n";// maybe false if none leftreturn0;}

五、复杂度分析

  • 插入 / 查找 / 前缀判断:对长度为 (L) 的单词为 (O(L))。
    (逐字符访问,最多做 L 次指针查找与数组索引。)

  • 删除:最坏情况也为 (O(L)),因为需要沿路径向下再递归回溯判断删除条件。

  • 空间复杂度:取决于树中结点数量。最坏情况(没有共享前缀)结点数等于所有单词长度之和,即 (\sum_{w\in S} |w|)。每个结点保存 26 个指针(或使用 map/哈希表以节省稀疏树的内存)。

  • 实际工程中可以通过以下方式优化内存:

    1. 把 children 从array<TrieNode*,26>换成vector<pair<char, TrieNode*>>unordered_map<char, TrieNode*>(节省稀疏树内存,但查找成本上升)。
    2. 使用内存池(pool allocator)减少频繁 new/delete 的开销。
    3. 使用压缩字典树(Radix Tree / Patricia Trie)合并只有一个孩子的链,减少结点数。
    4. 如果只处理小写字母且数据量大,使用array+bitset 带来时间优先的实现。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/23 12:01:13

U-Mamba终极教程:从零开始掌握医学影像分割神器

U-Mamba是一个革命性的深度学习框架&#xff0c;专门为生物信息学和医学影像分析领域设计。它结合了稀疏状态模型&#xff08;SSM&#xff09;和卷积神经网络的优势&#xff0c;在3D医学影像分割任务中表现出卓越的性能。本教程将带你从零开始&#xff0c;完整掌握这个强大的开…

作者头像 李华
网站建设 2026/7/24 2:15:35

优雅通知弹窗的终极解决方案:iziToast完全指南

优雅通知弹窗的终极解决方案&#xff1a;iziToast完全指南 【免费下载链接】iziToast Elegant, responsive, flexible and lightweight notification plugin with no dependencies. 项目地址: https://gitcode.com/gh_mirrors/iz/iziToast iziToast是一款优雅、响应式、…

作者头像 李华
网站建设 2026/7/21 14:05:13

2、英文写作中的语言与标点使用规范

英文写作中的语言与标点使用规范 在英文写作里,无论是日常交流、学术写作还是专业文档撰写,语言表达的准确性和规范性都至关重要。下面将为大家详细介绍英文写作中关于缩写词、动名词与分词、数字与数词、代词、技术缩写词与首字母缩写词、计量单位以及标点符号的使用规范。…

作者头像 李华
网站建设 2026/7/24 6:00:04

13、技术文档编写全解析

技术文档编写全解析 在技术领域,文档的编写至关重要,它能帮助用户更好地理解和使用产品。下面将详细介绍技术文档的各个部分、不同类型的技术文档以及编辑在文档编写中的作用。 1. 典型手册各部分的编辑格式 典型手册的各部分通常按照特定顺序排列,以下是各部分的详细介绍…

作者头像 李华
网站建设 2026/7/24 6:29:46

面试常考:如何原地重排数组?这个思路绝了

解题思路 这道题我们用两个指针分别追踪奇数位和偶数位,每次检查最后一个元素是奇数还是偶数,然后把它交换到对应的位置上。 比如最后一个元素是奇数,就把它换到下一个需要填充的奇数位(1, 3, 5…),换过来的元素又成为新的"最后一个元素",继续这个过程。 这样做的优势…

作者头像 李华
网站建设 2026/7/24 8:26:26

Wi-Fi CERTIFIED Multimedia™ (WMM®) 技术概述

1.0 概述 本文档定义了 WMM 的规范,WMM 是基于 IEEE 802.11e 标准补充 [2] 的 802.11 QoS 实现方案。最初提出 WMM 是为了防止因多个不兼容的 802.11e 预标准子集出现而导致的碎片化问题;部署 WMM 将为 802.11 语音、流媒体等服务提供可用的 QoS 功能。 1.1 参考文献 [1] …

作者头像 李华