AC自动机 字符串匹配 C语言

一、定义

AC自动机是一个复杂度为O(n) 的字符串匹配算法,能够实现快速多模板串匹配。
不多BB代码在文末
如待查找串有m个,分别为

模板串:
a
ab
abc

待匹配文本

模式串:
abcdcbab

则能快速找到

Matching "a" in pos 0, 6
Matching "ab" in pos 0, 6
Matching "abc" in pos 0

复杂度为三部分,
1、构建字典树,O(ml),m为模板串的个数,l为模板串的平均长度。
2、构建失配链接,O(ml)

3、文本串匹配,复杂度为O(n),n为文本串长度。
前两个部分为离线预处理,第三部分为在线处理,实际总体复杂度为线性。

二、AC自动机构建

简单来说,AC自动机就是在模式串上跑字典树,指针A在模式串上跑,指针B同步在字典树上跑,A只会线性跑一次,复杂度为线性。

2.1字典树(Trie)

2.1.1定义

字典树又叫前缀树,是一个通过边来存储字符串的树。这个数据结构把前缀相同的字符串放在一起,节省空间,所以又叫前缀树。
如有6个模板串,分别为

she
he
say
shr
her
yasherhs

可见该串有很多部分是重复的前缀,按顺序插入字典树,则树是这个样子的

在这里插入图片描述

字典树不在节点存储字符,而是通过边来存储单个字符。如只会出现26个字母的话,每个节点都有26个孩子,代表出现的字符。如没有使用的孩子节点则直接为空。实心的节点则为该数据。可通过数组来实现这个过程,将字典树完全逻辑化。

typedef _Trie
{
	char inf[MAXNODE][SIGMASIZE];
	int next_node[MAXNODE][SIGMASIZE];
	int value[MAXNODE];
	int cnt;
} Trie;

其中,MAXNODE为字典树可能使用到的最大节点数量。一般以ml*来计算就足够使用了;SIGMASIZE为字符串匹配任务中可能出现的不同字符的种类数量,如26个字母任务则为26,中文匹配则为256。
next_node[i][j] 代表,i节点的第j个孩子,的节点编号。如上图中,根节点为0,其第 ‘y’ 个孩子的编号为10,则

next_node[0]['y'] = 10;

实际使用中一般如下,将字符映射为数组下标。

#define index(c) ((c) - 'a')
next_node[0][index('y')] = 10;

如为中文则将每个文字视为2字符,每个字符映射到1~256+1#define index( c ) (( c ) + 129)

如上图例中字典树

next_node[5][index('r')] = 9;
next_node[1][index('h')] = 2;
next_node[1][index('a')] = 6;
...

结构体中其余成员变量分别为
value[i]: i节点中是否具有实际数据。上图例中,实心节点的value值为1或该键值的数量,空心节点中value值为-1。(不涉及AC自动机的话,用0也行)
inf[i]: i节点中的数据实体字符串。
cnt: 字典树实际使用节点数量。

2.1.2构建

for循环跑一遍模板串集就好了,每个字符串插入一遍,插入时也是for循环跑他一轮,用一个指针从root开始,在字典树上移动,遇到已存在的节点就移动,不存在的节点就cnt++开辟新的节点就好(并非真的malloc,而是逻辑开辟)。在最终节点上记录信息。

#define at(i, j) ((i) * SIGMASIZE + (j))
#define index(c) (*trie->INDEX)(c)

/*****************************************************************
 * Type name:   Trie字典树
 * Parameter:   next_node[i][j]: i节点的第j儿子的编号
 *              inf_id[i]:       i节点的信息对应外部字符串的编号
 *              value[i]:        i节点是否有信息/相同信息的个数
 *              size:            字典树实际使用节点数
 *              MAXNODE:         字典树可使用节点数上限
 *              SIGMASIZE:       每个节点的儿子数量
 *              INDEX:           函数指针,传入字符映射关系
 * Function:    Trie_init:       字典树初始化,需要传入字典树实例、开
 *                               辟节点个数、容许出现的字符种类数量、
 *                               字符映射关系回调函数
 *              Trie_insert:     插入字符串,需要传入字典树实例、待插
 *                               入字符串、该字符串在模板集中的编号
 *              Trie_destroy:    析构函数,清理内存。
 ****************************************************************/
typedef struct _Trie
{
	int *value;
	int *inf_id;
	int *next_node;
	int size;
	int MAXNODE;
	int SIGMASIZE;
	int (*INDEX)(char);
} Trie;

int Trie_init(Trie *trie, int MAXNODE, int SIGMASIZE, int (*INDEX)(char))
{
	int i = 0;
	trie->size = 1;	//初始有1个root节点,编号为0
	trie->MAXNODE = MAXNODE;
	trie->SIGMASIZE = SIGMASIZE;
	trie->INDEX = INDEX;

	/* 使用一维数组来实现二维数组 */
	trie->next_node = (int *)malloc(sizeof(int) * MAXNODE * SIGMASIZE);
	trie->inf_id = (int *)malloc(sizeof(int) * MAXNODE);
	trie->value = (int *)malloc(sizeof(int) * MAXNODE);

	for (i = 0; i < SIGMASIZE; i++)
	{
		trie->next_node[at(0, i)] = -1;
	}
	trie->value[0] = -1;

	return 0;
}

int Trie_insert(Trie *trie, char *str, int id)
{
	int c = 0;
	int i = 0, j = 0;
	int now = 0;
	int len = strlen(str);
	int SIGMASIZE = trie->SIGMASIZE;
	int *next_node = trie->next_node;
	int *value = trie->value;
	int *inf_id = trie->inf_id;

	for (i = 0; i < len; i++)
	{
		c = index(str[i]);
		if (next_node[at(now, c)] < 0)
		{
			next_node[at(now, c)] = trie->size;
			for (j = 0; j < SIGMASIZE; j++)
				next_node[at(trie->size, j)] = -1;
			value[trie->size] = -1;	//中间节点权值为-1
			trie->size++;
		}
		now = next_node[at(now, c)];
	}

	/* 这部分决定是否去重 */
	if (value[now] < 0)
		value[now] = 1;
	else
		value[now]++;
	inf_id[now] = id;

	return 0;
}

int Trie_destroy(Trie *trie)
{
	CHECK_NULL_POINTER(trie);

	if (NULL != trie->next_node)
		free(trie->next_node);
	trie->next_node = NULL;
	if (NULL != trie->value)
		free(trie->value);
	trie->value = NULL;
	if (NULL != trie->inf_id)
		free(trie->inf_id);
	trie->inf_id = NULL;

	return 0;
}

使用

int ind(char c);
inline int ind(char c){ return c - 'a'; }

int main()
{
	Trie trie;
	Trie_init(&trie, 100, 26, ind);
	Trie_insert(&trie, "she", 0);
	Trie_insert(&trie, "he", 1);
	Trie_insert(&trie, "say", 2);
	Trie_insert(&trie, "shr", 3);
	Trie_insert(&trie, "her", 4);
	Trie_destroy(&trie);
}

字典树构建完成。
字典树既可以做前缀树,也可以做后缀树,应在合适的时候选择合适的存储方法。

2.2将字典树转换成AC自动机

AC自动机实际上是对字典树的拓展,一般来说字典树的空儿子节点均为-1或者0,经过AC自动机的构建,空儿子节点可以去到,另一个模板串前缀当前已达节点的后缀相同的节点。
例如三个模板串分别为abcbcc,那么当文本中匹配到了abc的时候,理应认为同样匹配到了bcc。即当前已匹配部分具备另一个模板串的前缀。

考虑匹配一个模板串abc,模式串abd,暴力算法在匹配时会一个个的去对比:
模式串找到a,模板串找到a,匹配
模式串找到b,模板串找到b,匹配
模式串找到d,模板串找到c,不匹配
当遇到不匹配的时候,整个匹配过程又要从头开始,浪费时间。在字典树上的表现则是,now指针从root根节点开始,经过匹配,会走到树枝上,失配时,会直接回到根节点从头开始匹配。
而AC自动机则是构建失配链接,使得当找到d失配的情况下,能够直接快速跳转到以d为前缀的字符串的节点上。

模板串
she
he
say
shr
her
待匹配模式串
sshs

在这里插入图片描述

当读取到第一个s时,now指针从0节点走到1节点。
当读取到第二个s时,此时失配,没有任何模板串有ss前缀。则此时now指针应该仍处在1节点,其next_node[1][‘s’] = 1,因为ss已匹配后缀等于模板串的前缀,已经走过的路不应该重复走。
读取第三个字符h,now指针从1走到2。
读取第四个字符s,失配,但是匹配前缀s。now回到1。

同理,当走到3节点时,如遇到一个r,则应跳转至9节点。
在这里插入图片描述

只有当前后缀和任何前缀都不匹配时,才跳转回根节点。
完整构建AC自动机,则会得到一个比较复杂但是字典树的每个节点的子树都比较有用的 字典树。
上图模板集经过AC自动机构建,将变为下图的样子,实际则更为复杂。
在这里插入图片描述

红蓝绿线分别为第1、2、3层跳转关系。

2.3构建fail失配链接

首先增加一个失配链接fail数组,该数组指明当now处在i号节点时,如此时失配,跳转到fail[i]节点位置,然后再次进行匹配操作。fail数组是一个中间变量,构建AC自动机时使用,构建完成则不再使用。

失配链接的本质是,如果一个点i的fail指针指向j,那么root到j的字符串(前缀)是root到i的字符串的一个后缀。

如,根节点为第0层,由于第一层仅匹配了一个字符,任何字符都是自己的前缀,所以第一层所有的fail指针均指向根节点。当在第一层失配时,则回到根节点再次匹配。

next_node[now][i] = next_node[ fail[now] ][i];

比如now处在1节点,此时又遇到s,则next_node[1][‘s’] = next_node[root][‘s’];
匹配了的字符则继续往字典树下层走。
通过失配链接,则可以把下层的失配回流到上层某层重新进行匹配,而不是直接回到根。

在往下层走的同时,下层的失配链接等于当前节点失配时重新匹配的节点

fail[ next_node[now][i] ] = next_node[ fail[now] ][i];

fail[ next_node[1][‘h’] ] = next_node[ fail[1] ][‘h’]
fail[2] = next_node[0][‘h’]
fail[2] = 4
可见当在2号点失配时,则默认匹配了h,跳转到4号点继续运行。

具体方式通过广搜来构建,从第0层逐层向下递推。当走到某层的时候,该层的所有链接都已在上一层推出。推到底时,则AC自动机构建完成。
代码如下

首先在字典树数据结构中加入两个成员

typedef struct _Trie
{
	/* 字典树成员变量 */
	int *value;
	int *inf_id;
	int *next_node;
	int size;
	int MAXNODE;
	int SIGMASIZE;
	int (*INDEX)(char);

	/* AC自动机成员变量 */
	int *fail;
	int *last;
} Trie;
int Aho_corasick_build(Trie *trie)
{
	CHECK_NULL_POINTER(trie);
	int i = 0;
	Queue que;
	Queue_init(que, trie->MAXNODE);
	int SIGMASIZE = trie->SIGMASIZE;
	int *next_node = trie->next_node;
	int *value = trie->value;
	int *inf_id = trie->inf_id;

	trie->fail = (int *)malloc(sizeof(int) * trie->MAXNODE);
	CHECK_NULL_POINTER(trie->fail);
	trie->last = (int *)malloc(sizeof(int) * trie->MAXNODE);
	CHECK_NULL_POINTER(trie->last);
	int *fail = trie->fail;
	int *last = trie->last;

	fail[0] = 0;
	for (i = 0; i < SIGMASIZE; i++)
	{
		if (next_node[at(0, i)] == -1)
			next_node[at(0, i)] = 0;	//回到根节点
		else
		{
			fail[ next_node[at(0, i)] ] = 0;
			Queue_push(que, next_node[at(0, i)]);//广搜将第一层入队
		}
	}

	while (!Queue_empty(que))
	{
		int now = Queue_top(que);
		Queue_pop(que);//printf("\nnow=%d\n", now);
		for (i = 0; i < SIGMASIZE; i++)
		{
			if (next_node[at(now, i)] == -1)
				next_node[at(now, i)] = next_node[at( fail[now] , i)];
			else
			{
				fail[ next_node[at(now, i)] ] = next_node[at(fail[now], i)];
				Queue_push(que, next_node[at(now, i)]);
				//后缀链接。如果要去的是单词节点,last[下一个]就指向那个单词节点。
				//否则指向上一层的那个,直至根节点。
				if (value[ fail[ next_node[at(now, i)] ] ] > 0)
					last[ next_node[at(now, i)] ] = fail[ next_node[at(now, i)] ];
				else
					last[ next_node[at(now, i)] ] = last[ fail[ next_node[at(now, i)] ] ];
			}
		//	if (next_node[at(now, i)] > 0)
		//		printf("next[%d][%c]=%d last[%d]=%d\n", now, i + 'a', next_node[at(now, i)], now, last[now]);
		}
	}
	
	Queue_destroy(que);
	return 0;
}

代码中的last链接为后缀链接。如匹配到abc时,应认为bc、c均匹配成功。last[abc]=bc, last[bc]=c按顺序向上层映射,当找到某个词时,递归地向上查找即可拿到所有的和改词后缀相同的词。
构建结果,SIGMASIZE中未输出的都是直接跳转到根节点,为0

now=0
next[0][h]=4 last[0]=0
next[0][s]=1 last[0]=0

now=4
next[4][e]=5 last[4]=0
next[4][h]=4 last[4]=0
next[4][s]=1 last[4]=0

now=1
next[1][a]=6 last[1]=0
next[1][h]=2 last[1]=0
next[1][s]=1 last[1]=0

now=5
next[5][h]=4 last[5]=0
next[5][r]=9 last[5]=0
next[5][s]=1 last[5]=0

now=6
next[6][h]=4 last[6]=0
next[6][s]=1 last[6]=0
next[6][y]=7 last[6]=0

now=2
next[2][e]=3 last[2]=0
next[2][h]=4 last[2]=0
next[2][r]=8 last[2]=0
next[2][s]=1 last[2]=0

now=9
next[9][h]=4 last[9]=0
next[9][s]=1 last[9]=0

now=7
next[7][h]=4 last[7]=0
next[7][s]=1 last[7]=0

now=3
next[3][h]=4 last[3]=5
next[3][r]=9 last[3]=5
next[3][s]=1 last[3]=5

now=8
next[8][h]=4 last[8]=0
next[8][s]=1 last[8]=0

可对比字典树的例图,可见构建成功。其中last[3]=5则代表,3节点的she,其后缀具有5节点的he,即3词的后缀完整包含5词。

三、AC自动机运行

运行起来就轻松了。进入find函数去跑,因为整个字典树已经构建成闭环,遇到任何字符都可以直接在树上跑。
然后每个字符检查一下该节点是否有键值(value是否为1),如果有键值就进入递归打印阶段
递归打印阶段中,通过后缀链接递归地向上层找,直至根节点。(找到abc应认为找到了bc和c)
最终结果通过队列或者什么数据结构带出即可。

int recursion_print(Trie *trie, int now, int i, Queue *res)
{
	if (now > 0)
		if (trie->value[now] > 0)
		{
			int tmp = trie->value[now];
			Queue_push(*res, i);
			Queue_push(*res, trie->inf_id[now]);
		//	trie->value[now] = 0;	//1、
			tmp += recursion_print(trie, trie->last[now], i, res);
		//	trie->last[now] = 0;	//2、这两行决定查找到的模板是否重复计算
			return tmp;
		}
	return 0;
}

int Aho_corasick_find(Trie *trie, char *txt, Queue *res)
{
	CHECK_NULL_POINTER(trie);
	CHECK_NULL_POINTER(txt);
	int len = strlen(txt);
	int sum = 0, i = 0;
	int now = 0;
	int SIGMASIZE = trie->SIGMASIZE;
	int *next_node = trie->next_node;
	int *value = trie->value;
	int *inf_id = trie->inf_id;
	int *last = trie->last;

	for (i = 0; i < len; i++)
	{
		int ch = index(txt[i]);
		now = next_node[at(now, ch)];
		if (value[now] > 0)
			sum += recursion_print(trie, now, i, res);
		else
			if (last[now] > 0)
				sum += recursion_print(trie, last[now], i, res);
	}
	return sum;
}

如对上文提及的模板串集去跑"shesay"这个模式串,则可得到

shesay
In pos 2 get string[0]:she
In pos 2 get string[1]:he
In pos 5 get string[2]:say

四、完整代码与运行方法

完整c版代码如下,上节代码中使用的队列在头文件中有实现。
https://download.csdn.net/download/aaddggddaa/12910824

头文件

/*****************************************************************
 * File name:   ac_automaton.h
 ****************************************************************/
#ifndef __AC_AUTOMATON_H__
#define __AC_AUTOMATON_H__

/* 在int返回值时,进行是否为空判断 */
#define CHECK_NULL_POINTER(p)\
	do\
	{\
		if (NULL == (p))\
		{\
			printf("Pointer is equal to NULL\n");\
			return -1;\
		}\
	} while (0)

/*****************************************************************
 * Type name:   Trie字典树
 * Parameter:   next_node[i][j]: i节点的第j儿子的编号
 *              inf_id[i]:       i节点的信息对应外部字符串的编号
 *              value[i]:        i节点是否有信息/相同信息的个数
 *              size:            字典树实际使用节点数
 *              MAXNODE:         字典树可使用节点数上限
 *              SIGMASIZE:       每个节点的儿子数量
 *              INDEX:           函数指针,传入字符映射关系
 ****************************************************************/
typedef struct _Trie
{
	/* 字典树成员变量 */
	int *value;
	int *inf_id;
	int *next_node;
	int size;
	int MAXNODE;
	int SIGMASIZE;
	int (*INDEX)(char);

	/* AC自动机成员变量 */
	int *fail;
	int *last;
} Trie;

int Trie_init(Trie *trie, int MAXNODE, int SIGMASIZE, int (*INDEX)(char));
int Trie_destroy(Trie *trie);
int Trie_insert(Trie *trie, char *str, int id);


/*****************************************************************
 * Data type:   简单int循环队列
 * Function:    Queue_init:   初始化队列,申请空间
 *              Queue_push:   入队一个元素
 *              Queue_empty:  检查队列是否为空
 *              Queue_pop:    出队一个元素
 *              Queue_top:    查看队头元素
 *              Queue_destroy:释放空间
 ****************************************************************/
typedef struct _Queue
{
	int *queue_data;
	int phead, ptail;
	int size, MAXSIZE;
} Queue;

#define Queue_init(que, si)\
	do\
	{\
		(que).queue_data = NULL;\
		(que).queue_data = (int *)malloc(sizeof(int) * (si));\
		CHECK_NULL_POINTER((que).queue_data);\
		(que).phead = (que).ptail = 0;\
		(que).MAXSIZE = (si);\
		(que).size = 0;\
	} while (0)

#define Queue_push(que, num)\
	do\
	{\
		if ((que).size >= (que).MAXSIZE)\
			break;\
		(que).queue_data[(que).phead] = (num);\
		(que).phead = (que).phead + 1 >= (que).MAXSIZE ? 0 : (que).phead + 1;\
		(que).size++;\
	} while (0)

#define Queue_empty(que) (que).size <= 0 ? 1 : 0

#define Queue_pop(que)\
	do\
	{\
		if ((que).size <= 0)\
			break;\
		(que).size--;\
		(que).queue_data[(que).ptail] = 0;\
		(que).ptail = (que).ptail + 1 >= (que).MAXSIZE ? 0 : (que).ptail + 1;\
	} while (0)

#define Queue_top(que) (que).queue_data[(que).ptail]

#define Queue_destroy(que)\
	do\
	{\
		if (NULL != (que).queue_data)\
			free((que).queue_data);\
		(que).queue_data = NULL;\
		(que).phead = (que).ptail = 0;\
	} while (0)


/*****************************************************************
 * Type name:   AC自动机
 * Parameter:   fail[i]:     i节点的失配链接
 *              last[i]:     i节点的后缀链接
 ****************************************************************/
int Aho_corasick_build(Trie *trie);
int Aho_corasick_find(Trie *trie, char *txt, Queue *res);
int Aho_corasick_destroy(Trie *trie);

#endif

C文件

/*****************************************************************
 *                    Aho-Corasick automaton
 * File name:      ac_automaton.c
 * Note:           AC自动机,多模板串单模式串快速字符串匹配算法
 *                 已将模板文件解耦
 *                 查询结果通过头文件自带队列带出,队列中依次为匹
 *                 配到的位置和匹配字符串在模板集中的序号
 *                 头文件中自带队列实现
 * Use:            1、Trie t + Trie_init    声明字典树并初始化
 *                 2、Trie_insert           循环插入模板串
 *                 3、Aco_corasick_build    构建AC自动机
 *                 4、Queue + Queue_init    声明并初始化结果队列
 *                 5、Aco_corasick_find     AC自动机搜索模式串
 *                 6、Aco_corasick_destroy  AC自动机析构
 *                 7、Trie_destroy          字典树析构
 *                 8、Queue_destroy         结果队列析构
 * Author:         PlatixYe
 * Date:           2020.9.29
 ****************************************************************/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "ac_automaton.h"

/*****************************************************************
 * Data type:   Trie字典树部分
 * Function:    Trie_init:   字典树初始化,需要传入字典树实例、开
 *                           辟节点个数、容许出现的字符种类数量、
 *                           字符映射关系回调函数
 *              Trie_insert: 插入字符串,需要传入字典树实例、待插
 *                           入字符串、该字符串在模板集中的编号
 *              Trie_destroy:析构函数,清理内存。
 ****************************************************************/
#define at(i, j) ((i) * SIGMASIZE + (j))
#define index(c) (*trie->INDEX)(c)

int Trie_init(Trie *trie, int MAXNODE, int SIGMASIZE, int (*INDEX)(char))
{
	CHECK_NULL_POINTER(trie);
	int i = 0;
	trie->size = 1;	//初始有1个root节点,编号为0
	trie->MAXNODE = MAXNODE;
	trie->SIGMASIZE = SIGMASIZE;
	trie->INDEX = INDEX;

	/* 使用一维数组来实现二维数组 */
	trie->next_node = (int *)malloc(sizeof(int) * MAXNODE * SIGMASIZE);
	trie->inf_id = (int *)malloc(sizeof(int) * MAXNODE);
	trie->value = (int *)malloc(sizeof(int) * MAXNODE);

	for (i = 0; i < SIGMASIZE; i++)
	{
		trie->next_node[at(0, i)] = -1;
	}
	trie->value[0] = -1;

	return 0;
}

int Trie_insert(Trie *trie, char *str, int id)
{
	CHECK_NULL_POINTER(trie);
	CHECK_NULL_POINTER(str);
	int c = 0;
	int i = 0, j = 0;
	int now = 0;
	int len = strlen(str);
	int SIGMASIZE = trie->SIGMASIZE;
	int *next_node = trie->next_node;
	int *value = trie->value;
	int *inf_id = trie->inf_id;

	for (i = 0; i < len; i++)
	{
		c = index(str[i]);
		if (next_node[at(now, c)] < 0)
		{
			next_node[at(now, c)] = trie->size;
			for (j = 0; j < SIGMASIZE; j++)
				next_node[at(trie->size, j)] = -1;
			value[trie->size] = -1;	//中间节点权值为-1
			trie->size++;
		}
		now = next_node[at(now, c)];
	}

	/* 这部分决定是否去重 */
	if (value[now] < 0)
		value[now] = 1;
	else
		value[now]++;
	inf_id[now] = id;

	return 0;
}

int Trie_destroy(Trie *trie)
{
	CHECK_NULL_POINTER(trie);

	if (NULL != trie->next_node)
		free(trie->next_node);
	trie->next_node = NULL;
	if (NULL != trie->value)
		free(trie->value);
	trie->value = NULL;
	if (NULL != trie->inf_id)
		free(trie->inf_id);
	trie->inf_id = NULL;

	return 0;
}


/*****************************************************************
 * Data type: AC自动机部分
 * Function:  Aho_corasick_build:  将字典树构建为AC自动机,需要传
 *                                 入字典树实例
 *            Aho_corasick_find:   查询模式串中有多少匹配上的模板
 *                                 串需要传入字典树实例、待搜索字
 *                                 符串、接收结果用的自带队列
 *            Aho_corasick_destroy:析构函数,清理内存。
 ****************************************************************/
int Aho_corasick_build(Trie *trie)
{
	CHECK_NULL_POINTER(trie);
	int i = 0;
	Queue que;
	Queue_init(que, trie->MAXNODE);
	int SIGMASIZE = trie->SIGMASIZE;
	int *next_node = trie->next_node;
	int *value = trie->value;
	int *inf_id = trie->inf_id;

	trie->fail = (int *)malloc(sizeof(int) * trie->MAXNODE);
	CHECK_NULL_POINTER(trie->fail);
	trie->last = (int *)malloc(sizeof(int) * trie->MAXNODE);
	CHECK_NULL_POINTER(trie->last);
	int *fail = trie->fail;
	int *last = trie->last;

	fail[0] = 0;
	for (i = 0; i < SIGMASIZE; i++)
	{
		if (next_node[at(0, i)] == -1)
			next_node[at(0, i)] = 0;	//回到根节点
		else
		{
			fail[ next_node[at(0, i)] ] = 0;
			Queue_push(que, next_node[at(0, i)]);//广搜将第一层入队
		}
	}

	while (!Queue_empty(que))
	{
		int now = Queue_top(que);
		Queue_pop(que);//printf("\nnow=%d\n", now);
		for (i = 0; i < SIGMASIZE; i++)
		{
			if (next_node[at(now, i)] == -1)
				next_node[at(now, i)] = next_node[at( fail[now] , i)];
			else
			{
				fail[ next_node[at(now, i)] ] = next_node[at(fail[now], i)];
				Queue_push(que, next_node[at(now, i)]);
				//后缀链接。如果要去的是单词节点,last[下一个]就指向那个单词节点。
				//否则指向上一层的那个,直至根节点。
				if (value[ fail[ next_node[at(now, i)] ] ] > 0)
					last[ next_node[at(now, i)] ] = fail[ next_node[at(now, i)] ];
				else
					last[ next_node[at(now, i)] ] = last[ fail[ next_node[at(now, i)] ] ];
			}
		//	if (next_node[at(now, i)] > 0)
		//		printf("next[%d][%c]=%d last[%d]=%d\n", now, i + 'a', next_node[at(now, i)], now, last[now]);
		}
	}
	
	Queue_destroy(que);
	return 0;
}

int recursion_print(Trie *trie, int now, int i, Queue *res)
{
	if (now > 0)
		if (trie->value[now] > 0)
		{
			int tmp = trie->value[now];
			Queue_push(*res, i);
			Queue_push(*res, trie->inf_id[now]);
		//	trie->value[now] = 0;	//1、
			tmp += recursion_print(trie, trie->last[now], i, res);
		//	trie->last[now] = 0;	//2、这两行决定查找到的模板是否重复计算
			return tmp;
		}
	return 0;
}

int Aho_corasick_find(Trie *trie, char *txt, Queue *res)
{
	CHECK_NULL_POINTER(trie);
	CHECK_NULL_POINTER(txt);
	int len = strlen(txt);
	int sum = 0, i = 0;
	int now = 0;
	int SIGMASIZE = trie->SIGMASIZE;
	int *next_node = trie->next_node;
	int *value = trie->value;
	int *inf_id = trie->inf_id;
	int *last = trie->last;

	for (i = 0; i < len; i++)
	{
		int ch = index(txt[i]);
		now = next_node[at(now, ch)];
		if (value[now] > 0)
			sum += recursion_print(trie, now, i, res);
		else
			if (last[now] > 0)
				sum += recursion_print(trie, last[now], i, res);
	}
	return sum;
}

int Aho_corasick_destroy(Trie *trie)
{
	CHECK_NULL_POINTER(trie);

	if (NULL != trie->fail)
		free(trie->fail);
	trie->fail = NULL;
	if (NULL != trie->last)
		free(trie->last);
	trie->last = NULL;

	return 0;
}

使用方法

/*****************************************************************
 * File name: using.c 使用方法
 ****************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include "ac_automaton.h"

#define MAXWORD 100
#define SIGMASIZE 26

int ind(char c);
inline int ind(char c){ return c - 'a'; }

char str[5][5] ={ "she", "he", "say", "shr", "her" };
int main()
{
	Trie trie;
	int i = 0;
	Trie_init(&trie, MAXWORD, SIGMASIZE, ind);
	for (i = 0; i < 5; i++)
		Trie_insert(&trie, str[i], i);

	Aho_corasick_build(&trie);
	Queue res;
	Queue_init(res, MAXWORD);
	Aho_corasick_find(&trie, "shesay", &res);

	while (!Queue_empty(res))
	{
		int pos, id;
		pos = Queue_top(res);
		Queue_pop(res);
		id = Queue_top(res);
		Queue_pop(res);
		printf("In pos %d get string[%d]:%s\n", pos, id, str[id]);
	}
	Aho_corasick_destroy(&trie);
	Trie_destroy(&trie);
	Queue_destroy(res);
	return 0;
}

ACM单代码版

//AC自动机,CPP单文件版
#include<iostream>//可以查出被访问了多少个模板串。
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
const int Max_Node=1000002,Sigma_Size=26,muban_Size=52,muban_Num=10010;
int n;
char txt[Max_Node];
int idx(char c){return c-'a';}
class trie
{
public:
	int value[muban_Size*muban_Num];
	int fail[muban_Size*muban_Num];//失配指针。
	int last[muban_Size*muban_Num];//后缀链接。
	//char information[muban_Size*muban_Num][muban_Size];
	int next_node[muban_Size*muban_Num][Sigma_Size];
	int trie_size;
	trie()
	{
	// clear();
		trie_size=1;//  初始有1个根节点。根节点为0号点。	
		for(int i=0;i<Sigma_Size;i++)
			next_node[0][i]=-1;
		value[0]=-1;
	}
	void clear()
	{
		memset(next_node,0,(trie_size+10)*4);
		memset(fail,0,(trie_size+10)*4);
		memset(last,0,(trie_size+10)*4);
		memset(value,0,(trie_size+10)*4);
		trie_size=1;//  初始有1个根节点。根节点为0号点。
		for(int i=0;i<Sigma_Size;i++)
			next_node[0][i]=-1;
		value[0]=-1;
	}
	int insert(char s[])//字典树创建。
	{
		int now=0;
		int len=strlen(s);
		for(int i=0;i<len;i++)
		{
			if(next_node[now][idx(s[i])]<=0)
			{
				next_node[now][idx(s[i])]=trie_size;
				for(int j=0;j<Sigma_Size;j++)
					next_node[trie_size][j]=-1;
				value[trie_size]=-1;//中间节点权值为-1.
				trie_size++;
			}
			now=next_node[now][idx(s[i])];
		}
		if(value[now]<0)
			value[now]=1;
		else
			value[now]++;
		//   strcpy(information[now],s);
		return 0;
	}
	int Aho_Corasick_build()//将字典树创建成AC自动机。
	{
		queue<int> Q;
		fail[0]=0;
		for(int i=0;i<Sigma_Size;i++)
		{
			if(next_node[0][i]==-1)
				next_node[0][i]=0;
			else
			{
				fail[next_node[0][i]]=0;
				Q.push(next_node[0][i]);
			}
		}
		while(!Q.empty())
		{
			int now=Q.front();
			Q.pop();
			for(int i=0;i<Sigma_Size;i++)
			{
				if(next_node[now][i]==-1)
					next_node[now][i]=next_node[fail[now]][i];
				else
				{
					fail[next_node[now][i]]=next_node[fail[now]][i];
					Q.push(next_node[now][i]);
					//后缀链接。如果要去的节点是单词节点,last[改点]就指向那个单词节点。否则指向再上一层的那个,直至指向根节点。
					if(value[fail[next_node[now][i]]]>0)
						last[next_node[now][i]]=fail[next_node[now][i]];
					else
						last[next_node[now][i]]=last[fail[next_node[now][i]]];
				}
			}
		}
		return 0;
	}
	int find(char txt[])//AC自动机动起来!
	{
		int len=strlen(txt);
		int sum=0;
		int now=0;
		//   while(scanf("%c",&tamp_c)!=EOF&&(tamp_c!=10&&tamp_c!=13))
		for(int i=0;i<len;i++)
		{
			int ch=idx(txt[i]);
			now=next_node[now][ch];
			if(value[now]>0)
				sum+=print(now);
			else
				if(last[now]>0)
					sum+=print(last[now]);
		}
		return sum;
	}
	int print(int now)//递归按照后缀指针打印。
	{
		if(now>0)
		{
//            printf("Get : %s\n",information[now]);
			if(value[now]>0)
			{
				int tamp=value[now];
				//value[now]=0;	//1、
				tamp=print(last[now])+tamp;
				//last[now]=0;	//2、这两个东西决定了数据是否重复计算
				return tamp;
			}
			else return 0;
		}
		else return 0;
	}
}ac;

int main()
{
	int id;
	char s[muban_Size];
	scanf("%d",&id);
	while(id--)
	{
		scanf("%d",&n);
		for(int i=1;i<=n;i++)
		{
			scanf("%s",s);//puts(s);
			ac.insert(s);
		}
		//     scanf("%s",txt);
		ac.Aho_Corasick_build();
		scanf("%s",txt);
		printf("%d\n",ac.find(txt));
		//   ac.search(s);
		ac.clear();
	}
	return 0;
}
  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值