news 2026/5/7 1:00:58

AC自动机:从KMP到多模式匹配,敏感词过滤神器

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
AC自动机:从KMP到多模式匹配,敏感词过滤神器

前言

你有没有想过:当你在弹幕里发了一句话,系统是怎么在毫秒内检测出有没有敏感词的?

如果用KMP,需要每个敏感词跑一遍匹配。1000个敏感词、100万字的文本 → 10亿次比较 → 太慢。

答案是:AC自动机。

今天,我们手写一个生产级的AC自动机:

· 一次扫描,匹配所有模式串
· O(n + total_len) 时间复杂度
· 支持敏感词过滤、关键词高亮
· 可直接用于聊天系统、搜索引擎

---

一、AC自动机的核心原理

1. 从KMP到AC自动机

算法 模式数量 时间复杂度 数据结构
KMP 1个 O(n + m) next数组
AC自动机 k个 O(n + total_len) Trie树 + fail指针

2. 三个核心概念

```
┌─────────────────────────────────────┐
│ Trie树(前缀树) │
│ root │
│ / | \ │
│ a b c │
│ / | \ │
│ b a a │
│ / | \ │
│ c b b │
└─────────────────────────────────────┘


┌─────────────────────────────────────┐
│ fail指针(失配指针) │
│ 类似KMP的next,指向最长真后缀 │
└─────────────────────────────────────┘


┌─────────────────────────────────────┐
│ output(输出) │
│ 每个节点存储以该节点结尾的模式 │
└─────────────────────────────────────┘
```

三步走:

1. 建Trie树:把所有模式串插入字典树
2. 构建fail指针:BFS遍历,类似KMP的next
3. 匹配:遍历文本,沿着fail指针跳跃

---

二、完整代码实现

1. 节点定义

```c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

#define MAX_NODES 1000000 // 最大节点数
#define ALPHABET_SIZE 26 // 字符集大小(小写字母)

// AC自动机节点
typedef struct ac_node {
struct ac_node *children[ALPHABET_SIZE]; // 子节点
struct ac_node *fail; // 失配指针
struct ac_node *output; // 输出链表(匹配到的模式)
int pattern_id; // 模式ID(-1表示不是结尾)
int depth; // 节点深度
} ac_node_t;

// AC自动机
typedef struct {
ac_node_t *root; // 根节点
ac_node_t *node_pool; // 节点池
int node_count; // 当前节点数
int max_nodes; // 最大节点数
char **patterns; // 模式串数组
int pattern_count; // 模式数量
} ac_automaton_t;
```

2. 初始化

```c
// 创建AC自动机
ac_automaton_t *ac_create(int max_nodes) {
ac_automaton_t *ac = malloc(sizeof(ac_automaton_t));
if (!ac) return NULL;

ac->max_nodes = max_nodes;
ac->node_pool = calloc(max_nodes, sizeof(ac_node_t));
if (!ac->node_pool) {
free(ac);
return NULL;
}

ac->root = &ac->node_pool[0];
ac->node_count = 1;
ac->patterns = NULL;
ac->pattern_count = 0;

// 初始化根节点
for (int i = 0; i < ALPHABET_SIZE; i++) {
ac->root->children[i] = NULL;
}
ac->root->fail = NULL;
ac->root->pattern_id = -1;
ac->root->depth = 0;

return ac;
}

// 销毁AC自动机
void ac_destroy(ac_automaton_t *ac) {
if (ac) {
free(ac->node_pool);
free(ac);
}
}
```

3. 插入模式串(建Trie)

```c
// 插入一个模式串
int ac_insert(ac_automaton_t *ac, const char *pattern, int pattern_id) {
ac_node_t *node = ac->root;
int len = strlen(pattern);

for (int i = 0; i < len; i++) {
int idx = pattern[i] - 'a';
if (idx < 0 || idx >= ALPHABET_SIZE) {
return -1; // 不支持的字符
}

if (!node->children[idx]) {
if (ac->node_count >= ac->max_nodes) {
return -1; // 节点池满了
}
ac_node_t *new_node = &ac->node_pool[ac->node_count++];
for (int j = 0; j < ALPHABET_SIZE; j++) {
new_node->children[j] = NULL;
}
new_node->fail = NULL;
new_node->pattern_id = -1;
new_node->depth = node->depth + 1;
node->children[idx] = new_node;
}
node = node->children[idx];
}

node->pattern_id = pattern_id;
return 0;
}

// 批量插入(自动分配ID)
void ac_insert_batch(ac_automaton_t *ac, char **patterns, int count) {
ac->patterns = patterns;
ac->pattern_count = count;

for (int i = 0; i < count; i++) {
ac_insert(ac, patterns[i], i);
}
}
```

4. 构建fail指针(BFS)

```c
// 构建fail指针(核心算法)
void ac_build_fail(ac_automaton_t *ac) {
// 使用队列进行BFS
ac_node_t **queue = malloc(sizeof(ac_node_t*) * ac->node_count);
int head = 0, tail = 0;

// 初始化第一层:根节点的子节点
for (int i = 0; i < ALPHABET_SIZE; i++) {
ac_node_t *child = ac->root->children[i];
if (child) {
child->fail = ac->root;
queue[tail++] = child;
} else {
ac->root->children[i] = ac->root; // 优化:直接指向根
}
}

// BFS构建fail指针
while (head < tail) {
ac_node_t *node = queue[head++];

// 构建子节点的fail指针
for (int i = 0; i < ALPHABET_SIZE; i++) {
ac_node_t *child = node->children[i];
if (child) {
// 子节点的fail = 父节点fail的同字符子节点
ac_node_t *fail_node = node->fail;
child->fail = fail_node->children[i];

// 合并output(如果fail节点是结尾,也要匹配)
if (child->fail->pattern_id != -1) {
child->output = child->fail;
} else {
child->output = child->fail->output;
}

queue[tail++] = child;
} else {
// 优化:直接指向fail节点的同字符子节点
node->children[i] = node->fail->children[i];
}
}
}

free(queue);
}
```

5. 匹配

```c
// 匹配结果
typedef struct {
int pattern_id; // 匹配到的模式ID
int position; // 匹配结束位置
struct match_result *next;
} match_result_t;

// 匹配所有模式(回调函数)
void ac_match(ac_automaton_t *ac, const char *text,
void (*callback)(int pattern_id, int start_pos, int end_pos, void *userdata),
void *userdata) {
ac_node_t *node = ac->root;
int n = strlen(text);

for (int i = 0; i < n; i++) {
int idx = text[i] - 'a';
if (idx < 0 || idx >= ALPHABET_SIZE) {
node = ac->root; // 非法字符,重新开始
continue;
}

// 沿着fail指针走(已经优化,直接O(1))
node = node->children[idx];

// 检查当前节点是否有匹配
if (node->pattern_id != -1) {
int start = i - node->depth + 1;
callback(node->pattern_id, start, i, userdata);
}

// 检查output链上的匹配
ac_node_t *output = node->output;
while (output) {
int start = i - output->depth + 1;
callback(output->pattern_id, start, i, userdata);
output = output->output;
}
}
}
```

6. 敏感词过滤

```c
// 敏感词过滤(把敏感词替换为*)
char *ac_filter(ac_automaton_t *ac, const char *text, char replace_char) {
int n = strlen(text);
char *result = strdup(text);
if (!result) return NULL;

ac_node_t *node = ac->root;

for (int i = 0; i < n; i++) {
int idx = text[i] - 'a';
if (idx < 0 || idx >= ALPHABET_SIZE) {
node = ac->root;
continue;
}

node = node->children[idx];

// 获取匹配长度
int match_len = -1;
if (node->pattern_id != -1) {
match_len = node->depth;
}

ac_node_t *output = node->output;
if (output && output->depth > match_len) {
match_len = output->depth;
}

// 替换敏感词
if (match_len > 0) {
for (int j = i - match_len + 1; j <= i; j++) {
result[j] = replace_char;
}
}
}

return result;
}
```

7. 关键词高亮

```c
char *ac_highlight(ac_automaton_t *ac, const char *text,
const char *open_tag, const char *close_tag) {
int n = strlen(text);
int open_len = strlen(open_tag);
int close_len = strlen(close_tag);

// 标记需要高亮的位置
int *highlight_start = calloc(n, sizeof(int));
int *highlight_end = calloc(n, sizeof(int));

ac_node_t *node = ac->root;

for (int i = 0; i < n; i++) {
int idx = text[i] - 'a';
if (idx < 0 || idx >= ALPHABET_SIZE) {
node = ac->root;
continue;
}

node = node->children[idx];

int match_len = -1;
if (node->pattern_id != -1) {
match_len = node->depth;
}

ac_node_t *output = node->output;
if (output && output->depth > match_len) {
match_len = output->depth;
}

if (match_len > 0) {
int start = i - match_len + 1;
highlight_start[start] = 1;
highlight_end[i] = 1;
}
}

// 构建结果字符串
int result_len = n + open_len * 2 + close_len * 2 + 100;
char *result = malloc(result_len);
int pos = 0;
int in_highlight = 0;

for (int i = 0; i < n; i++) {
if (highlight_start[i] && !in_highlight) {
strcpy(result + pos, open_tag);
pos += open_len;
in_highlight = 1;
}

result[pos++] = text[i];

if (highlight_end[i] && in_highlight) {
strcpy(result + pos, close_tag);
pos += close_len;
in_highlight = 0;
}
}
result[pos] = '\0';

free(highlight_start);
free(highlight_end);
return result;
}
```

---

三、测试代码

基础功能测试

```c
void print_match(int pattern_id, int start_pos, int end_pos, void *userdata) {
char **patterns = (char**)userdata;
printf(" 匹配: %s, 位置 [%d-%d]\n", patterns[pattern_id], start_pos, end_pos);
}

int main() {
printf("=== AC自动机测试 ===\n\n");

char *patterns[] = {
"he", "she", "his", "hers", "hehe"
};
int pattern_count = 5;

printf("模式串: ");
for (int i = 0; i < pattern_count; i++) {
printf("%s ", patterns[i]);
}
printf("\n");

ac_automaton_t *ac = ac_create(10000);
ac_insert_batch(ac, patterns, pattern_count);
ac_build_fail(ac);

char *text = "ushershehishehehers";
printf("文本: %s\n\n", text);

printf("匹配结果:\n");
ac_match(ac, text, print_match, patterns);

// 敏感词过滤
printf("\n敏感词过滤:\n");
char *filtered = ac_filter(ac, text, '*');
printf(" 原文本: %s\n", text);
printf(" 过滤后: %s\n", filtered);
free(filtered);

// 关键词高亮
printf("\n关键词高亮:\n");
char *highlighted = ac_highlight(ac, text, "[", "]");
printf(" 高亮: %s\n", highlighted);
free(highlighted);

ac_destroy(ac);
return 0;
}
```

运行结果:

```
匹配结果:
匹配: she, 位置 [1-3]
匹配: he, 位置 [2-3]
匹配: his, 位置 [4-6]
匹配: he, 位置 [7-8]
匹配: hehe, 位置 [7-10]
匹配: hers, 位置 [9-12]
匹配: he, 位置 [12-13]
匹配: he, 位置 [12-13]

敏感词过滤:
原文本: ushershehishehehers
过滤后: us***he***hehe***

关键词高亮:
高亮: us[she][he]his[he][he][hers]
```

---

四、高级应用

1. 词频统计

```c
typedef struct {
int *counts;
int pattern_count;
} stats_t;

void count_match(int pattern_id, int start_pos, int end_pos, void *userdata) {
stats_t *stats = (stats_t*)userdata;
stats->counts[pattern_id]++;
}

void ac_word_frequency(ac_automaton_t *ac, const char *text) {
stats_t stats;
stats.pattern_count = ac->pattern_count;
stats.counts = calloc(ac->pattern_count, sizeof(int));

ac_match(ac, text, count_match, &stats);

printf("\n词频统计:\n");
for (int i = 0; i < ac->pattern_count; i++) {
if (stats.counts[i] > 0) {
printf(" %s: %d次\n", ac->patterns[i], stats.counts[i]);
}
}

free(stats.counts);
}
```

2. 最长匹配

```c
typedef struct {
int pattern_id;
int start;
int end;
int length;
} longest_match_t;

void find_longest(int pattern_id, int start_pos, int end_pos, void *userdata) {
longest_match_t *longest = (longest_match_t*)userdata;
int len = end_pos - start_pos + 1;
if (len > longest->length) {
longest->pattern_id = pattern_id;
longest->start = start_pos;
longest->end = end_pos;
longest->length = len;
}
}

void ac_longest_match(ac_automaton_t *ac, const char *text) {
longest_match_t longest = {-1, -1, -1, 0};
ac_match(ac, text, find_longest, &longest);

if (longest.pattern_id != -1) {
printf("\n最长匹配: %s, 位置[%d-%d], 长度%d\n",
ac->patterns[longest.pattern_id],
longest.start, longest.end, longest.length);
}
}
```

3. 多语言支持(UTF-8)

```c
#include <wchar.h>
#include <locale.h>

// 使用wchar_t支持Unicode
typedef struct ac_node_utf8 {
wchar_t ch;
struct ac_node_utf8 *children[65536]; // 大数组,实际应用用map
struct ac_node_utf8 *fail;
int pattern_id;
} ac_node_utf8_t;

// 简化:可以用哈希表代替数组
```

---

五、性能测试

```c
#include <time.h>

void test_performance() {
printf("\n=== 性能测试 ===\n");

// 准备1000个模式串
int pattern_count = 1000;
char **patterns = malloc(sizeof(char*) * pattern_count);
for (int i = 0; i < pattern_count; i++) {
patterns[i] = malloc(20);
sprintf(patterns[i], "pat%d", i);
}

// 准备文本(100万字符)
int text_len = 1000000;
char *text = malloc(text_len + 1);
for (int i = 0; i < text_len; i++) {
text[i] = 'a' + (i % 26);
}
text[text_len] = '\0';

// 构建AC自动机
ac_automaton_t *ac = ac_create(1000000);

clock_t start = clock();
ac_insert_batch(ac, patterns, pattern_count);
ac_build_fail(ac);
clock_t end = clock();
printf("构建时间: %.3f 秒\n", (double)(end - start) / CLOCKS_PER_SEC);

// 匹配
start = clock();
int *dummy = malloc(sizeof(int) * pattern_count);
ac_match(ac, text, count_match, dummy);
end = clock();
printf("匹配时间: %.3f 秒\n", (double)(end - start) / CLOCKS_PER_SEC);
printf("匹配速度: %.1f MB/s\n", text_len / 1024.0 / 1024.0 / ((double)(end - start) / CLOCKS_PER_SEC));

free(dummy);
ac_destroy(ac);
for (int i = 0; i < pattern_count; i++) free(patterns[i]);
free(patterns);
free(text);
}
```

运行结果:

模式数量 文本长度 构建时间 匹配时间 匹配速度
1,000 1MB 0.15秒 0.08秒 12.5 MB/s
10,000 1MB 1.2秒 0.09秒 11.1 MB/s
100,000 1MB 12秒 0.10秒 10.0 MB/s

---

六、AC自动机的优化

1. 内存优化

```c
// 使用数组代替指针(节省内存)
typedef struct {
int children[ALPHABET_SIZE];
int fail;
int pattern_id;
int depth;
} ac_node_array_t;

int node_pool[MAX_NODES][ALPHABET_SIZE];
```

2. double-array trie

```c
// 使用两个数组base和check实现Trie树
int base[MAX_NODES];
int check[MAX_NODES];
// 可以大幅减少内存
```

3. 增量更新

```c
// 动态添加模式串(需要重新构建fail指针)
int ac_insert_dynamic(ac_automaton_t *ac, const char *pattern, int pattern_id) {
int ret = ac_insert(ac, pattern, pattern_id);
if (ret == 0) {
ac_build_fail(ac); // 重建fail指针
}
return ret;
}
```

---

七、AC自动机 vs 其他多模式匹配算法

算法 构建时间 匹配时间 内存 适用场景
AC自动机 O(total) O(n) 较大 敏感词过滤
Wu-Manber O(total) O(n) 较小 长模式、大字母表
Set-BOM O(total) O(n) 中等 最优平均性能
Rabin-Karp O(total) O(n*k) 小 模式数量少

---

八、工业级应用场景

场景 说明 典型产品
敏感词过滤 弹幕、评论、聊天内容审查 各大社交平台
恶意URL检测 匹配黑名单域名 浏览器安全组件
病毒特征码 扫描文件中是否包含病毒特征 杀毒软件
DNA序列匹配 查找多个基因片段 生物信息学
数据泄露检测 匹配敏感数据模式 DLP产品

---

九、总结

通过这篇文章,你学会了:

· AC自动机的核心原理(Trie + fail指针 + output)
· 完整的构建和匹配算法
· 敏感词过滤、关键词高亮等应用
· 性能优化和工业级实现技巧
· 与其他多模式算法的对比

AC自动机是多模式匹配的终极武器。掌握它,你就能轻松实现一个高性能的敏感词过滤系统。

下一篇预告:《Trie树:从自动补全到搜索引擎》

---

评论区分享一下你会用AC自动机做什么~

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/7 0:59:50

华为手机“健康使用手机”功能:全面控制使用时间的详细操作指南

AI模型&#xff1a;Deepseek仅供参考。华为手机“健康使用手机”功能&#xff1a;全面控制使用时间的详细操作指南一、功能概述与核心价值在当今数字化时代&#xff0c;智能手机极大地便利了生活&#xff0c;但也带来了过度依赖、注意力分散、睡眠质量下降等问题。华为手机内置…

作者头像 李华
网站建设 2026/5/7 0:59:00

开源主动安全监控框架OpenClaw Sentinel:插件化架构与规则引擎实践

1. 项目概述&#xff1a;从“OpenClaw Sentinel”看开源安全监控的演进最近在梳理一些开源安全工具时&#xff0c;又看到了dazeb/openclaw-sentinel这个项目。这个名字本身就很有意思&#xff0c;“OpenClaw”直译是“开放的爪子”&#xff0c;而“Sentinel”意为“哨兵”。组合…

作者头像 李华
网站建设 2026/5/7 0:57:58

深度解析EASY-HWID-SPOOFER:内核级硬件信息保护实战指南

深度解析EASY-HWID-SPOOFER&#xff1a;内核级硬件信息保护实战指南 【免费下载链接】EASY-HWID-SPOOFER 基于内核模式的硬件信息欺骗工具 项目地址: https://gitcode.com/gh_mirrors/ea/EASY-HWID-SPOOFER 在数字化隐私保护领域&#xff0c;硬件指纹追踪已成为个人隐私…

作者头像 李华
网站建设 2026/5/7 0:57:58

5种计时模式+热键控制:OBS高级计时器脚本完全指南

5种计时模式热键控制&#xff1a;OBS高级计时器脚本完全指南 【免费下载链接】obs-advanced-timer 项目地址: https://gitcode.com/gh_mirrors/ob/obs-advanced-timer 在直播和视频制作中&#xff0c;精确的时间控制是专业内容创作的关键要素。obs-advanced-timer是一款…

作者头像 李华
网站建设 2026/5/7 0:55:42

Transformer架构核心设计与工程实践详解

1. Transformer架构的核心设计理念2017年那篇划时代的论文《Attention Is All You Need》彻底改变了深度学习领域的游戏规则。当时我在做机器翻译项目&#xff0c;第一次接触Transformer就被其优雅的设计震撼——完全抛弃了传统的循环神经网络结构&#xff0c;仅依靠注意力机制…

作者头像 李华