c++如何实现字符串过滤屏蔽词_c++ AC自动机算法与匹配查找【方法】
发布时间 - 2026-01-01 00:00:00 点击率:次不用std::string::find因时间复杂度高(O(n×m))、无法处理重叠匹配与前缀复用;AC自动机通过Trie树+失败指针+BFS构建,支持高效多模式匹配与完整子串覆盖。
为什么不用 std::string::find 做敏感词过滤
直接循环调用 find 查每个屏蔽词,时间复杂度是 O(n × m)(n 是文本长度,m 是词典总长度),遇到长文本+大词库(比如 10 万词)会明显卡顿。更麻烦的是,它无法处理“重叠匹配”和“前缀复用”,比如词典含 "ab" 和 "abc",文本为 "abc",find 可能只返回一次匹配,漏掉子串关系。
AC 自动机核心三步:构建失败指针 + 多模式匹配 + 输出优化
AC 自动机本质是 Trie 树 + BFS 构建的失败指针(fail),让匹配失败时能快速跳转到最长可匹配后缀节点。实际编码中,最容易出错的是 fail 指针初始化顺序和输出链(output link)的构建逻辑。
- 构建 Trie 时,每个节点存
children[256](或unordered_map),并标记is_end和id(对应哪个屏蔽词) - BFS 构建 fail:根节点子节点的
fail指向根;其余节点u的子节点v,其fail[v] = children[fail[u]][c],若不存在则回退到fail[fail[u]],直到根或找到 - 输出优化:每个节点额外存
out指针,指向最近一个真实匹配的终端节点(避免每次沿 fail 链向上遍历),可通过 BFS 时同步设置:out[u] = is_end[fail[u]] ? fail[u] : out[fail[u]]
struct Node {
Node* children[256] = {};
Node* fail = nullptr;
Node* out = nullptr; // 指向最近的终结节点
int id = -1; // 屏蔽词索引,-1 表示非终点
};
void build_ac_automaton(vector& patterns) {
// 步骤1:建Trie
root = new Node();
for (int i = 0; i < patterns.size(); ++i) {
Node* u = root;
for (char c : patterns[i]) {
if (!u->children[(unsigned char)c])
u->children[(unsigned char)c] = new Node();
u = u->children[(unsigned char)c];
}
u->id = i;
}
// 步骤2:BFS建fail和out
queuezuojiankuohaophpcnNode*youjiankuohaophpcn q;
root-youjiankuohaophpcnfail = root;
for (int c = 0; c zuojiankuohaophpcn 256; ++c) {
if (root-youjiankuohaophpcnchildren[c]) {
root-youjiankuohaophpcnchildren[c]-youjiankuohaophpcnfail = root;
root-youjiankuohaophpcnchildren[c]-youjiankuohaophpcnout = root-youjiankuohaophpcnchildren[c]-youjiankuohaophpcnid != -1 ? root-youjiankuohaophpcnchildren[c] : nullptr;
q.push(root-youjiankuohaophpcnchildren[c]);
} else {
root-youjiankuohaophpcnchildren[c] = root;
}
}
while (!q.empty()) {
Node* u = q.front(); q.pop();
for (int c = 0; c zuojiankuohaophpcn 256; ++c) {
Node* v = u-youjiankuohaophpcnchildren[c];
if (!v) continue;
Node* f = u-youjiankuohaophpcnfail;
while (f != root && !f-youjiankuohaophpcnchildren[c]) f = f-youjiankuohaophpcnfail;
v-youjiankuohaophpcnfail = f-youjiankuohaophpcnchildren[c];
v-youjiankuohaophpcnout = v-youjiankuohaophpcnfail-youjiankuohaophpcnid != -1 ? v-youjiankuohaophpcnfail : v-youjiankuohaophpcnfail-youjiankuohaophpcnout;
q.push(v);
}
}}
匹配时如何高效收集所有命中位置和词ID
单次扫描文本,每步更新当前节点,再沿 out 链收集所有匹配。注意:不能只检查当前节点 id,必须递归查 out,否则漏掉“abc”匹配时同时触发“bc”(如果词典里有)。
立即学习“C++免费学习笔记(深入)”;
- 匹配循环中,
cur = cur->children[(unsigned char)s[i]],若为空则跳到cur->fail继续找,直到成功或回到根 - 每次移动后,用临时指针
p = cur,while(p) { 记录 p->id;p = p->out; },这样能拿到所有后缀匹配项 - 若只需判断是否含屏蔽词(不关心位置),可设布尔标志 early-exit,一命中就返回 true
vector> find_all(const string& s) { // {pos, pattern_id} vector > res; Node* cur = root; for (int i = 0; i < s.size(); ++i) { unsigned char c = s[i]; while (cur != root && !cur->children[c]) cur = cur->fail; cur = cur->children[c] ? cur->children[c] : root; for (Node* p = cur; p; p = p-youjiankuohaophpcnout) { if (p-youjiankuohaophpcnid != -1) { res.emplace_back(i - patterns[p-youjiankuohaophpcnid].size() + 1,p-youjiankuohaophpcnid); } } } return res;
}
内存与编码细节:中文、大小写、特殊字符怎么处理
AC 自动机本身不关心字符语义,只依赖字节值。所以 UTF-8 中文会占多个字节,直接按
unsigned char索引会崩。常见解法是预处理:把 UTF-8 字符串转成 Unicode 码点序列(如用std::wstring_convert或 C++11,但后者已弃用),再用map替代数组。更轻量的做法是统一转小写+正则清洗后匹配,或用 ICU 库做标准化。
- 大小写不敏感?在插入词典前对每个
pattern调用transform(..., ::tolower),匹配时也对输入文本做同样转换- 允许模糊匹配(如星号通配)?AC 自动机不支持,得换 Aho-Corasick + NFA 扩展,或改用正则引擎(
std::regex性能差,慎用)- 高频更新词典?每次重建 AC 自动机开销大,可考虑双层结构:热词走哈希表快速匹配,冷词走 AC,或用增量式 fail 更新(极少实用)
真正上线时,最常被忽略的是
out指针的正确性验证——它必须指向 *某个真实终结节点*,而不是任意 fail 节点。建议加单元测试:用{"a", "ab", "bc"}和文本"abc",确保返回三个匹配(位置 0/0、0/1、1/2)。
# node # 编码 # 显卡 # 字节 # ai # c++ # 为什么 # red # asic # String # while # 字符串 # 递归 # char # int # 循环 # 指针 # Regex # map # transform # 算法 # 的是 # 或用 # 不关心 # 复用 # 多模 # 多个 # 遍历 # 只需 # 布尔
相关栏目: 【 网站优化151355 】 【 网络推广146373 】 【 网络技术251813 】 【 AI营销90571 】
相关推荐: 高防服务器:AI智能防御DDoS攻击与数据安全保障 如何在IIS中新建站点并配置端口与物理路径? Laravel如何使用Contracts(契约)进行编程_Laravel契约接口与依赖反转 Laravel如何集成Inertia.js与Vue/React?(安装配置) Laravel如何创建自定义Facades?(详细步骤) Laravel如何与Vue.js集成_Laravel + Vue前后端分离项目搭建指南 北京网页设计制作网站有哪些,继续教育自动播放怎么设置? 香港服务器网站测试全流程:性能评估、SEO加载与移动适配优化 js代码实现下拉菜单【推荐】 Laravel如何实现图片防盗链功能_Laravel中间件验证Referer来源请求【方案】 如何快速配置高效服务器建站软件? iOS验证手机号的正则表达式 晋江文学城电脑版官网 晋江文学城网页版直接进入 Midjourney怎么调整光影效果_Midjourney光影调整方法【指南】 使用豆包 AI 辅助进行简单网页 HTML 结构设计 高防服务器如何保障网站安全无虞? Laravel如何实现数据库事务?(DB Facade示例) Android中AutoCompleteTextView自动提示 Linux系统命令中tree命令详解 如何挑选优质建站一级代理提升网站排名? Laravel怎么实现观察者模式Observer_Laravel模型事件监听与解耦开发【指南】 胶州企业网站制作公司,青岛石头网络科技有限公司怎么样? php中::能调用final静态方法吗_final修饰静态方法调用规则【解答】 如何为不同团队 ID 动态生成多个非值班状态按钮 ,在苏州找工作,上哪个网站比较好? Laravel如何实现数据导出到PDF_Laravel使用snappy生成网页快照PDF【方案】 微信小程序 配置文件详细介绍 宙斯浏览器文件分类查看教程 快速筛选视频文档与图片方法 如何实现建站之星域名转发设置? JavaScript中的标签模板是什么_它如何扩展字符串功能 Win11怎么恢复误删照片_Win11数据恢复工具使用【推荐】 米侠浏览器网页图片不显示怎么办 米侠图片加载修复 企业在线网站设计制作流程,想建设一个属于自己的企业网站,该如何去做? 在线制作视频网站免费,都有哪些好的动漫网站? Win11怎么修改DNS服务器 Win11设置DNS加速网络【指南】 如何快速建站并高效导出源代码? 详解免费开源的DotNet二维码操作组件ThoughtWorks.QRCode(.NET组件介绍之四) 如何在IIS中新建站点并配置端口与IP地址? 制作企业网站建设方案,怎样建设一个公司网站? Laravel如何使用集合(Collections)进行数据处理_Laravel Collection常用方法与技巧 制作旅游网站html,怎样注册旅游网站? javascript中的try catch异常捕获机制用法分析 HTML5空格和nbsp有啥关系_nbsp的作用及使用场景【说明】 ChatGPT回答中断怎么办 引导AI继续输出完整内容的方法 WEB开发之注册页面验证码倒计时代码的实现 成都网站制作公司哪家好,四川省职工服务网是做什么用? 黑客如何通过漏洞一步步攻陷网站服务器? 图册素材网站设计制作软件,图册的导出方式有几种? Laravel的Blade指令怎么自定义_创建你自己的Laravel Blade Directives 如何正确下载安装西数主机建站助手?


p-youjiankuohaophpcnid);
}
}
}
return res;