杭州网络科技网站seo准
一、概述
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自动机”, “测试”,“失败指针”)是否存在,并且在什么地方
测试结果: