一、概述

Aho-Corasick算法是多模式匹配中的经典算法,目前在实际应用中较多。
Aho-Corasick算法对应的数据结构是Aho-Corasick自动机,简称AC自动机。
搞编程的一般都应该知道自动机FA吧,具体细分为:确定性有限状态自动机(DFA)和非确定性有限状态自动机NFA。普通的自动机不能进行多模式匹配,AC自动机增加了失败转移,转移到已经输入成功的文本的后缀,来实现。

在学习AC自动机之前我们必须要学习一下KMP算法和Trie树(字典树)

  • KMP算法:https://blog.csdn.net/qigaohua/article/details/113644864
  • Trie树: https://blog.csdn.net/qigaohua/article/details/113646006

二、什么是多匹配模式

多模式匹配就是有多个模式串P1,P2,P3…,Pm,求出所有这些模式串在连续文本T1…n中的所有可能出现的位置。
例如:求出模式集合{“nihao”,“hao”,“hs”,“hsr”}在给定文本"sdmfhsgnshejfgnihaofhsrnihao"中所有可能出现的位置。

三、AC自动机

使用Aho-Corasick算法需要三步:

  • 1.建立模式的Trie
  • 2.给Trie添加失败路径
  • 3.根据AC自动机,搜索待处理的文本

下面说明这三步:

3.1、建立多模式集合的Trie树

  当要插入许多模式的时候,我们要从前往后遍历整个字符串,当我们发现当前要插入的字符其节点再先前已经建成,直接去考虑下一个字符即可,当发现当前要插入的字符没有再其前一个字符所形成的树下没有自己的节点,我们就要创建一个新节点来表示这个字符,接下往下遍历其他的字符。然后重复上述操作。

例如括号中5个模式(she , he ,say, her, sh),对应的Trie树如下,其中绿色色标记的圈是表示为接收态

代码例子:

// 构建字典树, 添加模式
int ac_add_pattern(AC_MACHINE_p ac_machine, const char *str)
{int i, slen;unsigned char index;if (!ac_machine || !str ) return AC_PARAMETER_ERROR;if (!ac_machine->is_open)return AC_MACHINE_IS_CLOSED;AC_NODE_p p = ac_machine->root;slen = strlen(str);if (slen <= 0 || slen > MAX_STRING_LENGTH) return AC_STRING_LENGTH_ERROR;for (i = 0; i < slen; i++) {// index = str[i] - 'a';index = str[i];if (index > KIND - 1) return AC_PATTERN_LIMIT_ERROR;if (p->next[index] == NULL) {p->next[index] = (AC_NODE_p) malloc(sizeof(AC_NODE_t));memset(p->next[index], 0, sizeof(AC_NODE_t));}   p = p->next[index];}  if (p->final) return AC_DUPLICATE_PATTERN_ERROR;p->final = 1;p->pattern_no = ac_machine->pattern_count;if (ac_machine->pattern_count == ac_machine->pattern_capacity)ac_grow_pattern_vector(ac_machine);ac_machine->pattern[p->pattern_no].data = strdup(str);ac_machine->pattern[p->pattern_no].len = slen;ac_machine->pattern_count ++;return AC_SECCESS;
}

3.2、为多模式集合的Trie树添加失败路径

  KMP中,当比较到一个字符发现失配的时候我们会通过next数组,找到下一个开始匹配的位置,然后进行字符串匹配,当然KMP算法试用与单模式匹配,所谓单模式匹配,就是给出一个模式串,给出一个文本串,然后看模式串在文本串中是否存在。

  在AC自动机中,我们也有类似next数组的东西就是fail指针,当发现失配的字符失配的时候,跳转到fail指针指向的位置,然后再次进行匹配操作,AC自动机之所以能实现多模式匹配,就归功于Fail指针的建立。当前节点t有fail指针,其fail指针所指向的节点和t所代表的字符是相同的。因为t匹配成功后,我们需要去匹配t->child,发现失配,那么就从t->fail这个节点开始再次去进行匹配。
  Fail指针用BFS来求得,对于直接与根节点相连的节点来说,如果这些节点失配,他们的Fail指针直接指向root即可,其他节点Fail指针求法如下:假设当前节点为father,其孩子节点记为child。求child的Fail指针时,首先我们要找到其father的Fail指针所指向的节点,假如是t的话,我们就要看t的孩子中有没有和child节点所表示的字母相同的节点,如果有的话,这个节点就是child的fail指针,如果发现没有,则需要找father->fail->fail这个节点,然后重复上面过程,如果一直找都找不到,则child的Fail指针就要指向root。

如下图:

  • ①首先root最初会进队,然后root出队,我们把root的孩子的失败指针都指向root。因此h,s的fail指针都指向root(如红色线条所示,同时h,s进队)
  • ②接下来该h出队,我们就找h的孩子的fail指针,我们发现h这个节点其fail指针指向root,而root又没有字符为e的孩子,则e的fail指针是空的,如果为空,则也要指向root,(如图中蓝色线所示。并且e进队,此时s要出队)我们再找s的孩子a,h的fail指针,我们发现s的fail指针指向root,而root没有字符为a的孩子,故a的fail指针指向root,a入队,然后找h的fail指针,同样的先看s的fail指针是root,发现root又字符为h的孩子,所以h的fail指针就指向了第二层的h节点。e,a , h 的fail指针的指向如图蓝色线所示。此时队列中有e,a,h,e先出队,找e的孩子r的失败指针,我们先看e的失败指针,发现找到了root,root没有字符为r的孩子,则r的失败指针指向了root,并且r进队,然后a出队,我们也是先看a的失败指针,发现是root,则y的fail指针就会指向root.并且y进队。然后h出队,考虑h的孩子e,则我们看h的失败指针,指向第二层的h节点,看这个节点发现有字符值为e的节点,最后一行的节点e的失败指针就指向第三层的e。最后找r的指针,同样看第二层的h节点,其孩子节点不含有字符r,则会继续往前找h的失败指针找到了根,根下面的孩子节点也不存在有字符r,则最后r就指向根节点,最后一行节点的fail指针如绿色虚线所示。


代码例子:

// 设置failure指针
static int ac_set_failure(AC_MACHINE_p ac_machine)
{int i;int tail = 0, head = 0;AC_NODE_p p;AC_NODE_p ac_node_queue[50000];AC_NODE_p root = ac_machine->root;root->failure = NULL;ac_node_queue[head ++] = root;while (head != tail) {AC_NODE_p tmp = ac_node_queue[tail ++];for (i = 0; i < KIND; i ++) {if (tmp->next[i] != NULL) {if (tmp == root)tmp->next[i]->failure = root;else {p = tmp->failure;while (p) {if (p->next[i]) {tmp->next[i]->failure = p->next[i];break;}p = p->failure;}if (p == NULL)tmp->next[i]->failure = root;}ac_node_queue[head ++] = tmp->next[i];}}}return AC_SECCESS;
}

四、完整源码:(从任意文本中查询关键字)

ac.h

#ifndef __QGH_AC_H
#define __QGH_AC_H
#include <stdlib.h>#define KIND 256 // '0' = 48  'A' = 65 'a' = 97
#define BOOL int
#define False (0)
#define True (!(False))
#define MAX_STRING_LENGTH 1024typedef struct ac_node AC_NODE_t, *AC_NODE_p;
typedef struct ac_machine AC_MACHINE_t, *AC_MACHINE_p;struct ac_node {AC_NODE_t *next[KIND]; // 类似于字典树  AC_NODE_t *failure;    // 类似于kmp, 匹配失败时跳转的节点int final;      // 是否是一个字符串的结束int pattern_no; // 模式序号, 通过这个可以获得模式的详细信息
};typedef struct ac_text {const char *str;size_t len;
} AC_TEXT_t;typedef struct ac_pattern {const char *data;size_t len;// AC_PATTERN_p next;
} AC_PATTERN_t, *AC_PATTERN_p;typedef struct ac_match {AC_PATTERN_p pattern;int pattern_no; size_t position;
} AC_MATCH_t, *AC_MATCH_p;struct ac_machine {AC_NODE_p root;int pattern_count;     // 当前模式数量 int pattern_capacity;  // 总的容量AC_PATTERN_t *pattern; //保存所有添加的模式, 动态数组AC_NODE_p last_node;AC_TEXT_t text;size_t position;int work_mode; // enum ac_working_modeBOOL is_open;
};typedef enum ac_working_mode
{AC_WORKING_MODE_SEARCH = 0, /* default */AC_WORKING_MODE_FINDNEXT,
} AC_WORKING_MODE_t;enum {AC_SECCESS = 0,AC_STRING_LENGTH_ERROR,AC_PARAMETER_ERROR,AC_DUPLICATE_PATTERN_ERROR,AC_PATTERN_LIMIT_ERROR,AC_MACHINE_IS_CLOSED,AC_MACHINE_IS_OPEN,
};AC_MACHINE_p ac_machine_create();
void ac_machine_destory(AC_MACHINE_p ac_machine);
int ac_add_pattern(AC_MACHINE_p ac_machine, const char *str);
int ac_machine_finalize(AC_MACHINE_p ac_machine);
void ac_machine_settext(AC_MACHINE_p ac_machine, const char *text);
int ac_machine_search(AC_MACHINE_p ac_machine, AC_MATCH_p match);
AC_MATCH_t ac_machine_findnext(AC_MACHINE_p ac_machine);#endif

ac.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "ac.h"AC_MACHINE_p ac_machine_create()
{AC_MACHINE_p ac_machine;ac_machine  = (AC_MACHINE_p) malloc(sizeof(AC_MACHINE_t));if (!ac_machine) return NULL;ac_machine->root = (AC_NODE_p) malloc(sizeof(AC_NODE_t));if (ac_machine->root)memset(ac_machine->root, 0, sizeof(AC_NODE_t));else {free(ac_machine);return NULL;}ac_machine->pattern = NULL;;ac_machine->pattern_count = 0;ac_machine->pattern_capacity = 0;ac_machine->last_node = NULL;ac_machine->position = 0;ac_machine->work_mode = AC_WORKING_MODE_SEARCH;ac_machine->is_open = 1;return ac_machine;
}static void ac_root_free(AC_NODE_p ac_node)
{int i;if (ac_node) {for(i = 0; i < KIND; i++) {if (ac_node->next[i])ac_root_free(ac_node->next[i]);}free(ac_node);}
}void ac_machine_destory(AC_MACHINE_p ac_machine)
{int i;if (ac_machine) {ac_root_free(ac_machine->root);for (i = 0; i < ac_machine->pattern_count; i ++)free((void*)ac_machine->pattern[i].data);free(ac_machine->pattern);free(ac_machine);}
}static int ac_grow_pattern_vector(AC_MACHINE_p ac_machine)
{size_t grow = ac_machine->pattern_capacity == 0 ? 8 : ac_machine->pattern_capacity * 2;if (ac_machine->pattern_capacity == 0) {ac_machine->pattern_capacity = grow;ac_machine->pattern = (AC_PATTERN_p) malloc(ac_machine->pattern_capacity * sizeof(AC_PATTERN_t));if (!ac_machine->pattern) return -1;}else {AC_PATTERN_p p = ac_machine->pattern;ac_machine->pattern_capacity += grow;ac_machine->pattern = (AC_PATTERN_p) realloc(ac_machine->pattern, ac_machine->pattern_capacity * sizeof(AC_PATTERN_t));if (!ac_machine->pattern) {ac_machine->pattern = p;return -1;}}return 0;
}// 构建字典树, 添加模式
int ac_add_pattern(AC_MACHINE_p ac_machine, const char *str)
{int i, slen;unsigned char index;if (!ac_machine || !str ) return AC_PARAMETER_ERROR;if (!ac_machine->is_open)return AC_MACHINE_IS_CLOSED;AC_NODE_p p = ac_machine->root;slen = strlen(str);if (slen <= 0 || slen > MAX_STRING_LENGTH) return AC_STRING_LENGTH_ERROR;for (i = 0; i < slen; i++) {// index = str[i] - 'a';index = str[i];if (index > KIND - 1) return AC_PATTERN_LIMIT_ERROR;if (p->next[index] == NULL) {p->next[index] = (AC_NODE_p) malloc(sizeof(AC_NODE_t));memset(p->next[index], 0, sizeof(AC_NODE_t));}p = p->next[index];}if (p->final)return AC_DUPLICATE_PATTERN_ERROR;p->final = 1;p->pattern_no = ac_machine->pattern_count;if (ac_machine->pattern_count == ac_machine->pattern_capacity)ac_grow_pattern_vector(ac_machine);ac_machine->pattern[p->pattern_no].data = strdup(str);ac_machine->pattern[p->pattern_no].len = slen;ac_machine->pattern_count ++;return AC_SECCESS;
}// 设置failure指针
static int ac_set_failure(AC_MACHINE_p ac_machine)
{int i;int tail = 0, head = 0;AC_NODE_p p;AC_NODE_p ac_node_queue[50000];AC_NODE_p root = ac_machine->root;root->failure = NULL;ac_node_queue[head ++] = root;while (head != tail) {AC_NODE_p tmp = ac_node_queue[tail ++];for (i = 0; i < KIND; i ++) {if (tmp->next[i] != NULL) {if (tmp == root)tmp->next[i]->failure = root;else {p = tmp->failure;while (p) {if (p->next[i]) {tmp->next[i]->failure = p->next[i];break;}p = p->failure;}if (p == NULL)tmp->next[i]->failure = root;}ac_node_queue[head ++] = tmp->next[i];}}}return AC_SECCESS;
}int ac_machine_finalize(AC_MACHINE_p ac_machine)
{if (!ac_machine) return AC_PARAMETER_ERROR;ac_set_failure(ac_machine);// 禁止再向ac机中添加模式(pattern)ac_machine->is_open = 0;return AC_SECCESS;
}static void ac_machine_reset(AC_MACHINE_p ac_machine)
{ac_machine->last_node = ac_machine->root;ac_machine->position = 0;
}void ac_machine_settext(AC_MACHINE_p ac_machine, const char *text)
{ac_machine_reset(ac_machine);ac_machine->text.str = text;ac_machine->text.len = strlen(text);
}int ac_machine_search(AC_MACHINE_p ac_machine, AC_MATCH_p match)
{size_t position = 0;unsigned char index;AC_NODE_p p_node;AC_TEXT_t text = ac_machine->text;if (!ac_machine || !text.str) return AC_PARAMETER_ERROR;if (ac_machine->is_open) return AC_MACHINE_IS_OPEN;if (ac_machine->work_mode == AC_WORKING_MODE_FINDNEXT)position = ac_machine->position;    p_node = ac_machine->last_node;ac_machine_reset(ac_machine);while (position < text.len) {// index = str[position] - 'a';index = text.str[position];if (p_node->next[index] == NULL) {if (p_node == ac_machine->root)position ++;elsep_node = p_node->failure;}else {p_node = p_node->next[index];position ++;}if (p_node->final == 1) {// printf("match : %s\n", ac_machine->pattern[p_node->pattern_no].data);match->pattern = &ac_machine->pattern[p_node->pattern_no];match->position = position - match->pattern->len;match->pattern_no = p_node->pattern_no;if (ac_machine->work_mode == AC_WORKING_MODE_FINDNEXT) {ac_machine->position = position;ac_machine->last_node = p_node;}return AC_SECCESS;}}return 1;
}AC_MATCH_t ac_machine_findnext(AC_MACHINE_p ac_machine)
{AC_MATCH_t match;ac_machine->work_mode = AC_WORKING_MODE_FINDNEXT;match.pattern_no = -1;ac_machine_search(ac_machine, &match);ac_machine->work_mode = AC_WORKING_MODE_SEARCH;return match;
}#define TEST
#ifdef TEST
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{int i;// char haystack[] = "ushers测试下ABC12345";// char needle[][255]={//     "she","he","ABC", "his","hers"// };char needle[][255]={"KMP算法","字典树","AC自动机", "测试","失败指针"};AC_MACHINE_p ac_machine = ac_machine_create();AC_MATCH_t match;for (i = 0; i < (int)(sizeof(needle) / sizeof(needle[0])); i ++) {ac_add_pattern(ac_machine, needle[i]);}ac_machine_finalize(ac_machine);char buf[1024];int fd = open("test.txt", O_RDONLY);while (read(fd, buf, 1024)) {ac_machine_settext(ac_machine, buf);while((match = ac_machine_findnext(ac_machine)).pattern_no != -1) {printf("模式: %s  位置: %lu  序号: %d\n", match.pattern->data, match.position, match.pattern_no);}memset(buf, 0, 1024);}// ac_machine_search(ac_machine, &match);// printf("%s  %lu  %d\n", match.pattern->data, match.position, match.pattern_no);// ac_machine_search(ac_machine, &match);// printf("%s  %lu  %d\n", match.pattern->data, match.position, match.pattern_no);ac_machine_destory(ac_machine);return 0;
}#endif

五、编译测试:

在test.txt文本查询关键字(“KMP算法”,“字典树”,“AC自动机”, “测试”,“失败指针”)是否存在,并且在什么地方

测试结果:

Aho-Corasick 多模式匹配算法(AC自动机)相关推荐

  1. Aho-Corasick 多模式匹配算法(AC自动机) 的算法详解及具体实现

    多模式匹配 多模式匹配就是有多个模式串P1,P2,P3-,Pm,求出所有这些模式串在连续文本T1-.n中的所有可能出现的位置. 例如:求出模式集合{"nihao","ha ...

  2. go regexp匹配字符串_多模式字符串匹配算法ac自动机(用go语言实现)

    本文主要包括三部分内容 字典树 建立ac自动机 ac自动机匹配规则 字典树 字典树又称单词查找树,Trie树,是一种树形结构,是一种哈希树的变种. 典型应用是用于统计,排序和保存大量的字符串(但不仅限 ...

  3. 字符串匹配算法 -- AC自动机 基于Trie树的高效的敏感词过滤算法

    文章目录 1. 算法背景 2. AC自动机实现原理 2.1 构建失败指针 2.2 依赖失败指针过滤敏感词 3. 复杂度及完整代码 1. 算法背景 之前介绍过单模式串匹配的高效算法:BM和KMP 以及 ...

  4. ac自动机 匹配最长前缀_AC自动机算法

    AC自动机简介: 首先简要介绍一下AC自动机:Aho-Corasick automation,该算法在1975年产生于贝尔实验室,是著名的多模匹配算法之一.一个常见的例子就是给出n个单词,再给出一段包 ...

  5. 字符串匹配算法(AC自动机 Aho-Corasick)

    文章目录 1. 多模式串匹配 2. 经典多模式串匹配--AC自动机 2.1 AC自动机构建 2.2 在AC自动机上匹配主串 2.3 复杂度分析 3. python包 1. 多模式串匹配 前面学的BF. ...

  6. 模板 - AC自动机

    ACM-ICPC模板 目录 求有多少个模式串在文本串里出现过 建fail树dfs求每个模式串在文本串中的出现次数 ac自动机fail树上dfs序建可持久化线段树 AC自动机是一种多模匹配算法 AC自动 ...

  7. 提高篇 第二部分 字符串算法 第4章 AC自动机

    https://blog.csdn.net/wangyh1008/article/details/81428056 [模板]AC自动机(加强版) 洛谷3796 AC自动机_A_loud_name-CS ...

  8. 【算法无用系列】AC自动机敏感词过滤

    简介: 本文是博主自身对AC自动机的原理的一些理解和看法,主要以举例的方式讲解,同时又配以相应的图片.代码实现部分也予以明确的注释,希望给大家不一样的感受.AC自动机主要用于多模式字符串的匹配,本质上 ...

  9. Aho-Corasick 多模式匹配算法、AC自动机详解

    Aho-Corasick算法是多模式匹配中的经典算法,目前在实际应用中较多. Aho-Corasick算法对应的数据结构是Aho-Corasick自动机,简称AC自动机. 搞编程的一般都应该知道自动机 ...

  10. ac自动机 匹配最长前缀_Aho Corasick自动机结合DoubleArrayTrie极速多模式匹配

    本文使用Double Array Trie实现了一个性能极高的Aho Corasick自动机,应用于分词可以取得1400万字每秒,约合27MB/s的分词速度.其中词典为150万词,构建耗时1801 m ...

最新文章

  1. python自动化测试平台github_GitHub - DangKaio/FXTest: 接口自动化测试平台——python+flask版,支持http协议...
  2. python打印进度条starting...done_python打印进度条-tqdm
  3. 【案例】无线串口模块 星型网络组网 案例
  4. Taro+react开发(17)--注意编译
  5. python 水仙花_python 水仙花
  6. HTTP(1)---HTTP 工作原理
  7. mybatis plus使用in查询
  8. 《编程珠玑,字字珠玑》910读书笔记——代码优化
  9. Masonry详解(转)
  10. 处理11gR2 RAC集群资源状态异常INTERMEDIATE,CHECK TIMED OUT
  11. 挑战性题目DSCT302:求不同形态的平衡二叉树数目
  12. 免安装mysql配置环境变量_mysql——免安装配置
  13. word公式编辑器复制粘贴未响应_word2016一复制公式就卡死?
  14. PayPal完成对区块链公司的投资首秀
  15. window 10 局域网同步时间(解决错误:This scheduler instance is still active but was recovered by another instanc)
  16. 网络公益信息特征和用户行为规律研究以微博为例
  17. MacW资讯:苹果MAC电脑便笺字体和颜色怎么修改
  18. 全屏功能-实现全屏-图标切换
  19. U盘安装CentOS7 解决各种问题
  20. SQL中实用的小技巧

热门文章

  1. c.cnzz.com core.php,谷歌浏览器下cnzz统计代码报错被拦截
  2. 悼念侯耀文:慢慢消失的笑声
  3. 史丰收速算 (蓝桥杯)
  4. 小米手机 VS 阿里手机,阿里输在512MB内存,华为Honor只有电池容量跟小米差不多...
  5. Linux安全合规性检查和加固
  6. Python 3 执行 MIUI 线刷包内 flash_gen_crc_list.py 文件
  7. 通过DCMM评估对企业有什么作用
  8. miui12系统小米系统安卓系统里面存储空间占用太多如何清理
  9. java罗马数字_Java实现的求解经典罗马数字和阿拉伯数字相互转换问题示例
  10. 23种设计模式(自学笔记)