1. 简介

AC自动机是一种多模匹配的文本匹配算法。

如果采用naive的方法,即依次比较文本串s中是否包含模式串p1, p2,...非常耗时。考虑到这些模式串中可能具有相同子串,可以利用已经比较过的那些模式串的一些信息,来优化效率。容易想到的一种方法是为这些模式串构建一个trie树,可以较好的利用模式串的公共前缀信息。

但是如果只是采用普通的trie树,仍有 如果一个模式串P1不匹配,就要重新回到根节点再找下一个模式串P2,也就是对于下一个模式串P2,要从P2的起始元素开始,依次与文本串S比较。这同样不够高效。P2如果可以利用和P1的一些共性信息,使得可以从P2的尽可能靠后的元素开始,与文本串S比较,那么算法的时间复杂度可能会有效降低。

AC自动机采用了KMP算法找next的思路,为trie树中每个节点找fail节点。

KMP中next与AC中fail的区别:

KMP算法 中 next[j] = k 表示前缀 [0 ~ (k - 1)] 与 后缀 [(j - k) ~ (j - 1)] 这k个元素是对应相等的,但是P[j] 和 P[k] 是不一定相等要在下一次进行比较的;

AC自动机中则是 cur = cur.fail,也即从 [根节点 ~ cur.fail (包含cur.fail节点)] 这个前缀,与 [cur节点所在路径上某节点 ~ cur节点(包含cur节点)] 这个后缀,对应相等。

2. 算法原理

(转自参考链接1)

2.1 初识AC自动机

AC自动机的基础是Trie树。和Trie树不同的是,树中的每个结点除了有指向孩子的指针(或者说引用),还有一个fail指针,它表示输入的字符与当前结点的所有孩子结点都不匹配时(注意,不是和该结点本身不匹配),自动机的状态应转移到的状态(或者说应该转移到的结点)。fail指针的功能可以类比于KMP算法中next数组的功能。

我们现在来看一个用目标字符串集合{abd,abdk, abchijn, chnit, ijabdf, ijaij}构造出来的AC自动机

上图是一个构建好的AC自动机,其中根结点不存储任何字符,根结点的fail指针为null。虚线表示该结点的fail指针的指向,所有表示字符串的最后一个字符的结点外部都用红圈表示,我们称该结点为这个字符串的终结结点。每个结点实际上都有fail指针,但为了表示方便,本文约定一个原则,即所有指向根结点的 fail虚线都未画出

从上图中的AC自动机,我们可以看出一个重要的性质:每个结点的fail指针表示由根结点到该结点所组成的字符序列的所有后缀 和 整个目标字符串集合(也就是整个Trie树)中的所有前缀 两者中最长公共的部分

比如图中,由根结点到目标字符串“ijabdf”中的 ‘d’组成的字符序列“ijabd”的所有后缀在整个目标字符串集{abd,abdk, abchijn, chnit, ijabdf, ijaij}的所有前缀中最长公共的部分就是abd,而图中d结点(字符串“ijabdf”中的这个d)的fail正是指向了字符序列abd的最后一个字符。

2.2 AC自动机的运行过程

1表示当前结点的指针指向AC自动机的根结点,即curr = root

2从文本串中读取(下)一个字符

3当前结点的所有孩子结点中寻找与该字符匹配的结点,

若成功:判断当前结点以及当前结点fail指向的结点是否表示一个字符串的结束,若是,则将文本串中索引起点记录在对应字符串保存结果集合中(索引起点= 当前索引-字符串长度+1)。curr指向该孩子结点,继续执行第2步

若失败:执行第4步。

4若fail == null(说明目标字符串中没有任何字符串是输入字符串的前缀,相当于重启状态机)curr = root, 执行步骤2,

否则,将当前结点的指针指向fail结点,执行步骤3)

2.3  例子

来一个具体的例子加深理解,初始时当前结点为root结点,我们现在假设文本串text = “abchnijabdfk”。

图中的紫色实曲线表示了整个搜索过程中的当前结点指针的转移过程,结点旁的文字表示了当前结点下读取的文本串字符。比如初始时,当前指针指向根结点时,输入字符‘a’,则当前指针指向结点a,此时再输入字符‘b’,自动机状态转移到结点b,……,以此类推。图中AC自动机的最后状态只是恰好回到根结点,并不一定都会回到根节点。

需要说明的是,当指针位于结点b(图中曲线经过了两次b,这里指第二次的b,即目标字符串“ijabdf”中的b),这时读取文本串字符下标为9的字符(即‘d’)时,由于b的所有孩子结点(这里恰好只有一个孩子结点)中存在能够匹配输入字符d的结点,那么当前结点指针就指向了结点d,而此时该结点d的fail指针指向的结点又恰好表示了字符串“abc”的终结结点(用红圈表示),所以我们找到了目标字符串“abc”一次。这个过程我们在图中用虚线表示,但状态没有转移到“abd”中的d结点。

在输入完所有文本串字符后,我们在文本串中找到了目标字符串集合中的abd一次,位于文本串中下标为7的位置;目标字符串ijabdf一次,位于文本串中下标为5的位置。

3. 构造AC自动机的方法与原理

首先我们将所有的目标字符串插入到Trie树中然后通过广度优先遍历为每个结点的所有孩子节点的fail指针找到正确的指向

确定fail指针指向的问题和KMP算法中构造next数组的方式如出一辙。具体方法如下

1)将根结点的所有孩子结点的fail指向根结点,然后将根结点的所有孩子结点依次入列。

2)若队列不为空:

2.1)出列,我们将出列的结点记为curr, failTo表示curr的fail指向的结点,即failTo = curr.fail

2.2) a.判断curr.child[i] == failTo.child[i]是否成立,

成立:curr.child[i].fail = failTo.child[i],

不成立:判断 failTo == null是否成立

成立: curr.child[i].fail == root

不成立:执行failTo = failTo.fail,继续执行2.2)

b.curr.child[i]入列,再次执行再次执行步骤2)

若队列为空:结束

4. 代码实现

 #coding:utf-8
import queue class Node(object):
def __init__(self):
self.children = {}
self.fail = None
self.isWord = False
self.word = "" class ACAutomation(object):
""" AC Automation
"""
def __init__(self):
self.root = Node() def add(self, word):
cur_node = self.root
for char in word:
if char not in cur_node.children:
cur_node.children[char] = Node()
cur_node = cur_node.children[char]
cur_node.isWord = True
cur_node.word = word def link_fail(self):
que = queue.Queue()
que.put(self.root) while que.empty() == False: cur_node = que.get()
cur_fail = cur_node.fail for child_key, child_value in cur_node.children.items(): while True:
if cur_fail is None:
cur_node.children[child_key].fail = self.root
break elif child_key in cur_fail.children:
cur_node.children[child_key].fail = cur_fail.children[child_key]
break else:
cur_fail = cur_fail.fail que.put(cur_node.children[child_key]) def curWords(self, cur_node):
""" 该函数为查找当前节点处所有可能的匹配的模式串的集合
Args:
cur_node 当前节点
Returns:
set 当前节点处所有可能的匹配的模式串的集合 匹配成功模式串有两种情况:
1. 当前节点处 isWord = True, 则匹配的模式串即为 cur_node.word (如图例'ijabdf')
2. 当前节点的fail节点处 isWord = True, 则匹配的模式串为 cur_node.fail.word
(如图例 'abd',文字标红处有解释)
(当然fail节点也可能有fail.fail...需要while循环继续推一下.)
"""
ret = set()
cur_fail = cur_node.fail
if cur_node.isWord:
ret.add(cur_node.word)
#ret.append(cur_node.word)
while cur_fail is not None and cur_fail is not self.root:
if cur_fail.isWord:
#ret.append(cur_fail.word)
ret.add(cur_fail.word)
cur_fail = cur_fail.fail
return ret def search(self, s):
cur_node = self.root
# result = []
result = set()
cur_pos = 0 while cur_pos < len(seq):
word = seq[cur_pos]
#result.extend(self.curWords(cur_node)) while word in cur_node.children == False and cur_node != self.root:
# result.extend(self.curWords(cur_node))
result |= self.curWords(cur_node)
cur_node = cur_node.fail if word in cur_node.children:
#result.extend(self.curWords(cur_node))
result |= self.curWords(cur_node)
cur_node = cur_node.children[word] else:
cur_node = self.root result |= self.curWords(cur_node) cur_pos += 1 return list(result) ac = ACAutomation()
l = ['abd', 'abdk', 'abchijn', 'chnit', 'ijabdf', 'ijaij']
for e in l:
ac.add(e)
ac.link_fail()
seq = 'abchnijabdfk'
ret = ac.search(seq)
print(ret) """output
"""
# ['abd', 'ijabdf']

参考链接:

1. 多模字符串匹配算法之AC自动机—原理与实现:https://www.cnblogs.com/nullzx/p/7499397.html

2. 从头到尾彻底理解KMP:https://www.cnblogs.com/zhangtianq/p/5839909.html

[Alg] 文本匹配-多模匹配-AC自动机的更多相关文章

  1. 【JSOI2007】文本生成器 题解(AC自动机+动态规划)

    题目链接 题目大意:给定$n$个子串,要求构造一个长度为$m$的母串使得至少有一个子串是其子串.问方案数. ------------------------ 我们可以对要求进行转化:求出不合法的方案数 ...

  2. AC自动机算法详解 (转载)

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

  3. AC自动机讲解+[HDU2222]:Keywords Search(AC自动机)

    首先,有这样一道题: 给你一个单词W和一个文章T,问W在T中出现了几次(原题见POJ3461). OK,so easy~ HASH or KMP 轻松解决. 那么还有一道例题: 给定n个长度不超过50 ...

  4. BZOJ1195[HNOI2006]最短母串——AC自动机+BFS+状态压缩

    题目描述 给定n个字符串(S1,S2,„,Sn),要求找到一个最短的字符串T,使得这n个字符串(S1,S2,„,Sn)都是T的子串. 输入 第一行是一个正整数n(n<=12),表示给定的字符串的 ...

  5. HDU 2222 Keywords Search(AC自动机)题解

    题意:给你几个keywords,再给你一段文章,问你keywords出现了几次. 思路:这里就要用到多模匹配算法AC自动机了,AC自动机需要KMP和字典树的知识,匹配时是在字典树上,失配我们就要用到类 ...

  6. 「kuangbin带你飞」专题十七 AC自动机

    layout: post title: 「kuangbin带你飞」专题十七 AC自动机 author: "luowentaoaa" catalog: true tags: - ku ...

  7. [NOI2011][bzoj2434] 阿狸的打字机 [AC自动机+dfs序+fail树+树状数组]

    题面 传送门 正文 最暴力的 最暴力的方法:把所有询问代表的字符串跑一遍kmp然后输出 稍微优化一下:把所有询问保存起来,把模板串相同的合并,求出next然后匹配 但是这两种方法本质没有区别,都是暴力 ...

  8. 【AC自动机】玄武密码

    [题目链接] https://loj.ac/problem/10058 [题意] 对于每一段文字,其前缀在母串上的最大匹配长度是多少呢 [参考别人的题解] https://www.luogu.org/ ...

  9. AC 自动机学习笔记

    虽然 NOIp 原地爆炸了,目前进入 AFO 状态,但感觉省选还是要冲一把,所以现在又来开始颓字符串辣 首先先复习一个很早很早就学过但忘记的算法--自动 AC AC自动机. AC 自动机能够在 \(\ ...

随机推荐

  1. webgrind安装使用详细说明

    webgrind是一个网页版的性能分析工具,它的主要作用就是分析xdebug生成的cachegrind文件,以一种界面友好详尽的方式来展示性能数据.试用了一下感觉还是很不错的,鉴于网上并没有一个系统介 ...

  2. JXJJOI2018_T1_market

    题目描述 某天Lemon去超市买柠檬,他发现货架上有N个柠檬,每个柠檬都有一个重量Wi和价格Ci. Lemon身上只带了S元钱,因此他想要买一个价格不超过S的柠檬回家,另外,他希望他买的那个柠檬的性价 ...

  3. mysql启动报错ERROR! The server quit without updating PID file处理

    从其它服务器拷贝编译安装后的MySQL5.7目录后启动时报错如下: ERROR! The server quit without updating PID file(/path/to/XXX.pid) ...

  4. Intellij IDEA 干货分享

    更多视频详情:https://www.bilibili.com/video/av89385013/ Intellij IDEA 真是越用越强大 它总是在我们写代码的时候 不时给我们来个小惊喜 出于对 ...

  5. USB小白学习之路(7) FPGA Communication with PC by CY7C68013,TD_init()解析

    注:这个TD_Init()只对EP6进行了配置,将其配置成为Bluk_In端口,而没有对EP2进行配置.这篇文章直接把寄存器的图片贴上来了,看起来比较杂.感兴趣的可以看下一篇文章,是转自CSDN,对E ...

  6. 【5min+】保持程序健康的秘诀!AspNetCore的HealthCheck

    系列介绍 [五分钟的dotnet]是一个利用您的碎片化时间来学习和丰富.net知识的博文系列.它所包含了.net体系中可能会涉及到的方方面面,比如C#的小细节,AspnetCore,微服务中的.net ...

  7. CVE-2019-0708 远程桌面漏洞复现

    漏洞影响Windows版本: Windows XP SP3 x86Windows XP Professional x64 Edition SP2Windows XP Embedded SP3 x86W ...

  8. mysql 存储过程 执行存储过程修改了表中所有行的信息

    存储过程中的where条件语句,如果传入的参数和表字段名相同,存储过程就会把这个约束条件忽略.小结:存储过程中传递的参数名不要和字段名相同.特别是修改.删除等操作,可能会对整张表产生影响.后果会很严重 ...

  9. VUE实现Studio管理后台(七):树形结构,文件树,节点树共用一套代码NodeTree

    本次介绍的内容,稍稍复杂了一点,用VUE实现树形结构.目前这个属性结构还没有编辑功能,仅仅是展示.明天再开一篇文章,介绍如何增加编辑功能,标题都想好了.先看今天的展示效果: 构建树必须用到递归,使用s ...

  10. The superclass "javax.servlet.http.HttpServlet" was not found on the Java Build Path index.jsp页面出现错误的解决方法

    点击项目名称>>>点击Buid Path>>>点击右侧add  library>>>点击Server  Runtime>>>点击 ...