简介

首先简要介绍一下AC自动机:Aho-Corasick automation,该算法在1975年产生于贝尔实验室,是著名的多模匹配算法之一。一个常见的例子就是给出n个单词,再给出一段包含m个字符的文章,让你找出有多少个单词在文章里出现过。要搞懂AC自动机,先得有模式树(字典树)Trie和KMP模式匹配算法的基础知识。AC自动机算法分为3步:构造一棵Trie树,构造失败指针和模式匹配过程。

多模匹配

AC自动机(Aho-Corasick Automaton)是多模匹配算法的一种。所谓多模匹配,是指在字符串匹配中,模式串有多个。前面所介绍的KMP、BM为单模匹配,即模式串只有一个。假设主串T[1⋯m]T[1⋯m],模式串有kk个P=P1,⋯,PkP=P_1,⋯,P_k,且模式串集合的总长度为nn。如果采用KMP来匹配多模式串,则算法复杂度为:

O(|P1|+m+⋯+|Pk|+m)=O(n+km)

O(|P_1|+m+⋯+|P_k|+m)=O(n+km)

而KMP并没有利用到模式串之间的重复字符结构信息,每一次的匹配都需要将主串从头至尾扫描一遍。因此,贝尔实验室的Aho与Corasick于1975年结合KMP与有限状态机(finite state machines)的思想,提出AC自动机算法[1]。

AC算法

思想

自动机按照文本字符顺序,接受字符,并发生状态转移。这些状态缓存了“按照字符转移成功(但不是模式串的结尾)”、“按照字符转移成功(是模式串的结尾)”、“按照字符转移失败”三种情况下的跳转与输出情况,因而降低了复杂度。

基本构造

AC算法中有三个核心函数,分别是:

  • success; 成功转移到另一个状态(也称goto表或success表)
  • failure; 不可顺着字符串跳转的话,则跳转到一个特定的节点(也称failure表),从根节点到这个特定的节点的路径恰好是失败前的文本的一部分。
  • emits; 命中一个模式串(也称output表)

举例

以经典的ushers为例,模式串是he/ she/ his /hers,文本为“ushers”。构建的自动机如图:

其实上图省略了到根节点的fail边,完整的自动机如下图:

匹配过程

自动机从根节点0出发

  1. 首先尝试按success表转移(图中实线)。按照文本的指示转移,也就是接收一个u。此时success表中并没有相应路线,转移失败。
  2. 失败了则按照failure表回去(图中虚线)。按照文本指示,这次接收一个s,转移到状态3。
  3. 成功了继续按success表转移,直到失败跳转步骤2,或者遇到output表中标明的“可输出状态”(图中红色状态)。此时输出匹配到的模式串,然后将此状态视作普通的状态继续转移。

算法高效之处在于,当自动机接受了“ushe”之后,再接受一个r会导致无法按照success表转移,此时自动机会聪明地按照failure表转移到2号状态,并经过几次转移后输出“hers”。来到2号状态的路不止一条,从根节点一路往下,“h→e”也可以到达。而这个“he”恰好是“ushe”的结尾,状态机就仿佛是压根就没失败过(没有接受r),也没有接受过中间的字符“us”,直接就从初始状态按照“he”的路径走过来一样(到达同一节点,状态完全相同)。

构造过程

看来这三个表很厉害,不过,它们是怎么计算出来的呢?

goto表

很简单,了解一点trie树知识的话就能一眼看穿,goto表就是一棵trie树。把上图的虚线去掉,实线部分就是一棵trie树了。

output表

output表也很简单,与trie树里面代表这个节点是否是单词结尾的结构很像。不过trie树只有叶节点才有“output”,并且一个叶节点只有一个output。下图却违背了这两点,这是为什么呢?其实下图的output会在建立failure表的时候进行一次拓充。

failure表

这个表是trie树没有的,加了这个表,AC自动机就看起来不像一棵树,而像一个图了。failure表是状态与状态的一对一关系,别看图中虚线乱糟糟的,不过你仔细看看,就会发现节点只会发出一条虚线,它们严格一对一。

这个表的构造方法是:

  1. 首先规定与状态0距离为1(即深度为1)的所有状态的fail值都为0。
  2. 然后设当前状态是S1,求fail(S1)。我们知道,S1的前一状态必定是唯一的(刚才说的一对一),设S1的前一状态是S2,S2转换到S1的条件为接受字符C,测试S3 = goto(fail(S2), C)。
  3. 如果成功,则fail(S1) = goto(fail(S2), C) = S3。
  4. 如果不成功,继续测试S4 = goto(fail(S3), C)是否成功,如此重复,直到转换到某个有效的状态Sn,令fail(S1) = Sn。

算法实现

# -*- encoding=utf-8 -*- __all__ = ['Ahocorasick', ]class Node(object):def __init__(self):self.next = {}self.fail = Noneself.isWord = Falseclass Ahocorasick(object):def __init__(self):self.__root = Node()def addWord(self, word):'''@param word: add word to Tire tree 添加关键词到Tire树中'''tmp = self.__rootfor i in range(0, len(word)):if not tmp.next.has_key(word[i]):tmp.next[word[i]] = Node()tmp = tmp.next[word[i]]tmp.isWord = Truedef make(self):'''build the fail function 构建自动机,失效函数'''tmpQueue = []tmpQueue.append(self.__root)while(len(tmpQueue) > 0):temp = tmpQueue.pop()p = Nonefor k, v in temp.next.items():if temp == self.__root:temp.next[k].fail = self.__rootelse:p = temp.failwhile p is not None:if p.next.has_key(k):temp.next[k].fail = p.next[k]breakp = p.failif p is None :temp.next[k].fail = self.__roottmpQueue.append(temp.next[k])def search(self, content):'''@return: a list of tuple,the tuple contain the match start and end index'''p = self.__rootresult = []startWordIndex = 0endWordIndex = -1currentPosition = 0while currentPosition < len(content):word = content[currentPosition]# 检索状态机,直到匹配while p.next.has_key(word) == False and p != self.__root:p = p.failif p.next.has_key(word):if p == self.__root:# 若当前节点是根且存在转移状态,则说明是匹配词的开头,记录词的起始位置startWordIndex = currentPosition# 转移状态机的状态p = p.next[word]else:p = self.__rootif p.isWord:# 若状态为词的结尾,则把词放进结果集result.append((startWordIndex, currentPosition))currentPosition += 1return resultdef replace(self, content):replacepos = self.search(content)result = contentfor i in replacepos:result = result[0:i[0]] + (i[1] - i[0] + 1) * u'*' + content[i[1] + 1:]return resultif __name__ == '__main__':ah = Ahocorasick()text = raw_input("text: ")patterns = raw_input("pattern: ")words = patterns.split(" ")for w in words:ah.addWord(w)ah.make()results = ah.search(text)print resultsif len(results) == 0:print "No find."else:print len(results)," matching results are listed below."print "-------" + "-"*len(text) + "-------"print textcount = 0for site in results:w = text[site[0]:site[1]+1]count += 1print " "*site[0] + w + " "*(len(text)-site[1]) + "  " + str(site[0]) + "  " + str(count)print "-------" + "-"*len(text) + "-------"

参考

  • 码农场
  • https://github.com/metadata1984/pyAhocorasick
  • http://www.cnblogs.com/en-heng/p/5247903.html
  • Aho-corasick讲解
  • http://dsqiu.iteye.com/blog/1700312

字符串匹配算法 之 Aho-Corasick相关推荐

  1. java 字符串匹配_多模字符串匹配算法原理及Java实现代码

    多模字符串匹配算法在这里指的是在一个字符串中寻找多个模式字符字串的问题.一般来说,给出一个长字符串和很多短模式字符串,如何最快最省的求出哪些模式字符串出现在长字符串中是我们所要思考的.该算法广泛应用于 ...

  2. 算法之「字符串匹配算法」

    前言 一说到两个字符串匹配,我们很自然就会想到用两层循环来匹配,用这种方式就可以实现一个字符串是否包含另一个字符串了,这种算法我们称为 BF算法. BF算法 BF算法,即暴力(Brute Force) ...

  3. TypeScript:Aho–Corasick算法实现敏感词过滤

    敏感词过滤应该是许多后端同事经常会遇到的需求,无论是评论.弹幕.文章,都需要做敏感词过滤处理来规避风险.在前端开发中,使用replace函数来替换字符串是我们的常规操作,在这之前我思考过如果用Java ...

  4. 字符串匹配算法 -- BM(Boyer-Moore) 和 KMP(Knuth-Morris-Pratt)详细设计及实现

    文章目录 1. 算法背景 2. BM(Boyer-Moore)算法 2.1 坏字符规则(bad character rule) 2.2 好后缀规则(good suffix shift) 2.3 复杂度 ...

  5. Go 语言实现字符串匹配算法 -- BF(Brute Force) 和 RK(Rabin Karp)

    今天介绍两种基础的字符串匹配算法,当然核心还是熟悉一下Go的语法,巩固一下基础知识 BF(Brute Force) RK(Rabin Karp) 源字符串:src, 目标字符串:dest: 确认des ...

  6. Boyer-Moore 字符串匹配算法

    字符串匹配问题的形式定义: 文本(Text)是一个长度为 n 的数组 T[1..n]: 模式(Pattern)是一个长度为 m 且 m≤n 的数组 P[1..m]: T 和 P 中的元素都属于有限的字 ...

  7. Java实现算法导论中朴素字符串匹配算法

    朴素字符串匹配算法沿着主串滑动子串来循环匹配,算法时间性能是O((n-m+1)m),n是主串长度,m是字串长度,结合算法导论中来理解,具体代码参考: package cn.ansj;public cl ...

  8. 字符串匹配算法Java_如何简单理解字符串匹配算法?

    这篇文章来说说如何简单理解KMP,BM算法.之前看过一些文章说,KMP算法很难理解. 可我并不觉得. 我反而觉得它容易理解.平时我们写java代码的时候, 判断一个字符串是否存在包含另一个字符串都是直 ...

  9. BF,KMP,BM三种字符串匹配算法性能比较

    三种最基本的字符串匹配算法是BF,KMP以及BM,BF算法是最简单直接的匹配算法,就是逐个比较,一旦匹配不上,就往后移动一位,继续比较,所以比较次数很都. 关于KMP和BM的详细介绍可以参考下面的两个 ...

  10. Python:对字符串匹配算法的分析

    问题描述 字符串匹配问题可以归纳为如下的问题: 在长度为n的文本T[1-n]中,查找一个长度为m的模式P[1-m].并且假设T,P中的元素都来自一个有限字母集合Ʃ.如果存在位移s,其中0≤s≤n-m, ...

最新文章

  1. Zabbix 3.2.6 通过SNMP和iDRAC监控DELL服务器
  2. leetcode算法题--会议室★★
  3. Boost:验证atomic <>没有对void指针提供算术运算
  4. JVM解释器和编译器
  5. 第二周CoreiDRAW总结
  6. 互联网晚报 | 2月11日 星期五 | 小红书月活跃用户超2亿;小鹏汽车宣布拓展欧洲市场;贝壳首个青年公寓项目落地上海...
  7. 跳台阶问题:动态规划,公式
  8. 在集合中根据条件来筛选数据
  9. 关于程序分析和代码编写
  10. SMOTE(Synthetic Minority Over-Sampling Technique ,即“人工少数类过采样法“)----Python调包简单实现
  11. 美区苹果id关闭双重认证_双重认证
  12. Unity手游实战:从0开始SLG——本地化篇(一)聊聊游戏本地化
  13. 图片Exif信息解析(Java实现)
  14. 基于ServiceStage的微服务开发与部署(二)
  15. 锂离子电池电压特性研究
  16. 厂家深度解读:采用凯夫拉中底的劳保鞋优点有哪些?
  17. HTML5讲解与演示转载整理
  18. 链表,队列,堆栈的区别
  19. hive报错return code 2 from org.apache.hadoop.hive.ql.exec.mr.MapRedTask
  20. matplotlib基础(4)之饼图 pie

热门文章

  1. Unity 3D学习(基础篇)——C#基础入门
  2. 《科学》最新研究:给“薛定谔猫”第二个盒子会发生什么?
  3. myssql基于Spring Boot的宠物猫店管理系统的设计与实现毕业设计源码140909
  4. 黄帝内经.素问.脉要精微论篇(17)
  5. C++中mian中的参数
  6. main()打成mian()的后果,切记小心
  7. 赴日IT工程师长期招聘中
  8. 东南计算机专硕和学硕,问了200个学长学姐,终于知道专硕学硕的“差别”
  9. 美国波士顿大学计算机专业排名,2017年波士顿大学各专业最新排名榜单 美国名校排名!...
  10. 微型计算机的一般结构,微型计算机的基本结构