0

0

c++如何实现字符串过滤屏蔽词_c++ AC自动机算法与匹配查找【方法】

冰火之心

冰火之心

发布时间:2026-01-01 09:16:02

|

325人浏览过

|

来源于php中文网

原创

不用std::string::find因时间复杂度高(O(n×m))、无法处理重叠匹配与前缀复用;AC自动机通过Trie树+失败指针+BFS构建,支持高效多模式匹配与完整子串覆盖。

c++如何实现字符串过滤屏蔽词_c++ ac自动机算法与匹配查找【方法】

为什么不用 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_endid(对应哪个屏蔽词)
  • 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++免费学习笔记(深入)”;

讯飞智作-讯飞配音
讯飞智作-讯飞配音

讯飞智作是一款集AI配音、虚拟人视频生成、PPT生成视频、虚拟人定制等多功能的AI音视频生产平台。已广泛应用于媒体、教育、短视频等领域。

下载
  • 匹配循环中,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)。

相关文章

c++速学教程(入门到精通)
c++速学教程(入门到精通)

c++怎么学习?c++怎么入门?c++在哪学?c++怎么学才快?不用担心,这里为大家提供了c++速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

更多
string转int
string转int

在编程中,我们经常会遇到需要将字符串(str)转换为整数(int)的情况。这可能是因为我们需要对字符串进行数值计算,或者需要将用户输入的字符串转换为整数进行处理。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

312

2023.08.02

while的用法
while的用法

while的用法是“while 条件: 代码块”,条件是一个表达式,当条件为真时,执行代码块,然后再次判断条件是否为真,如果为真则继续执行代码块,直到条件为假为止。本专题为大家提供while相关的文章、下载、课程内容,供大家免费下载体验。

81

2023.09.25

js 字符串转数组
js 字符串转数组

js字符串转数组的方法:1、使用“split()”方法;2、使用“Array.from()”方法;3、使用for循环遍历;4、使用“Array.split()”方法。本专题为大家提供js字符串转数组的相关的文章、下载、课程内容,供大家免费下载体验。

248

2023.08.03

js截取字符串的方法
js截取字符串的方法

js截取字符串的方法有substring()方法、substr()方法、slice()方法、split()方法和slice()方法。本专题为大家提供字符串相关的文章、下载、课程内容,供大家免费下载体验。

205

2023.09.04

java基础知识汇总
java基础知识汇总

java基础知识有Java的历史和特点、Java的开发环境、Java的基本数据类型、变量和常量、运算符和表达式、控制语句、数组和字符串等等知识点。想要知道更多关于java基础知识的朋友,请阅读本专题下面的的有关文章,欢迎大家来php中文网学习。

1435

2023.10.24

字符串介绍
字符串介绍

字符串是一种数据类型,它可以是任何文本,包括字母、数字、符号等。字符串可以由不同的字符组成,例如空格、标点符号、数字等。在编程中,字符串通常用引号括起来,如单引号、双引号或反引号。想了解更多字符串的相关内容,可以阅读本专题下面的文章。

609

2023.11.24

java读取文件转成字符串的方法
java读取文件转成字符串的方法

Java8引入了新的文件I/O API,使用java.nio.file.Files类读取文件内容更加方便。对于较旧版本的Java,可以使用java.io.FileReader和java.io.BufferedReader来读取文件。在这些方法中,你需要将文件路径替换为你的实际文件路径,并且可能需要处理可能的IOException异常。想了解更多java的相关内容,可以阅读本专题下面的文章。

547

2024.03.22

php中定义字符串的方式
php中定义字符串的方式

php中定义字符串的方式:单引号;双引号;heredoc语法等等。想了解更多字符串的相关内容,可以阅读本专题下面的文章。

539

2024.04.29

php源码安装教程大全
php源码安装教程大全

本专题整合了php源码安装教程,阅读专题下面的文章了解更多详细内容。

7

2025.12.31

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
HTML5/CSS3/JavaScript/ES6入门课程
HTML5/CSS3/JavaScript/ES6入门课程

共102课时 | 6.6万人学习

前端基础到实战(HTML5+CSS3+ES6+NPM)
前端基础到实战(HTML5+CSS3+ES6+NPM)

共162课时 | 18.5万人学习

第二十二期_前端开发
第二十二期_前端开发

共119课时 | 12.1万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号