1.版权说明

商业转载请联系作者获得授权,非商业转载请注明出处。
本文作者:Q-WHai
发表日期: 2015年10月24日
本文链接:http://blog.csdn.net/lemon_tree12138/article/details/49335051
来源:CSDN
更多内容:分类 >> 算法与数学

2.概述

Aho-Corasick automaton(后面心均以AC代替),该算法在1975年产生于贝尔实验室,是著名的多模匹配算法之一。

AC自动机算法分为3步:构造一棵Trie树,构造失效指针和模式匹配过程。而这3步就是AC自动机算法的精髓所在,分别对应我们后面马上要说的3个函数:success,failure和emits.

3.特别说明

3.0.学前导读

在学习本文之前,需要两个方面的知识背景。一个是Trie树,一个是KMP算法。大家可以移步到两面的两个链接中,学习一下。之后,回过头来再看我们的AC自动机,就可以会比较容易消化,也能更容易理解其中的精髓。

《数据结构:字典树的基本使用》

《算法:模式匹配之KMP算法》

3.1.本文参考

《Aho-Corasick算法的Java实现与分析-码农场》

4.一睹为快

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

5.原理说明

5.0.算法比较

正如前面所说,AC算法是基于Trie树且是KMP模式匹配算法的扩展。那么这里我们就可以从两个方面来作为切入点,详细说明一下AC算法或是AC自动机究竟是何物。

首先明白两点:Trie树的核心点是状态转移,KMP模式匹配的核心点是减少重复匹配

先说Trie树吧。在之前的博客中,我还是用了很多的篇幅来说Trie树,不算这一篇的话,也有3篇文章或多或少都和Trie树扯上点边儿。前面的Trie树中,每个节点既是字符本身,又是节点的状态(DAT则不是这样)。节点为字符本身,这个好理解,那又是节点的状态这个要怎么解释呢?因为我们知道,当我们在遍历的过程中,走到某一个点的时候,比如说:目前有两个字典字符串:T1:"abcde"和T2:"abdef",当我们在遍历的过程中走了"abcd"且停在了'd'字符上.这个时候,我们可以认定目前是处于字符串T1上的。因为当前节点可以代表其状态。而在T1和T2中,两个'd'节点的状态是不同的。而Trie树的状态转移则可以理解为,我们在遍历到节点d的时候,动态确定节点d的下一个状态,即节点e。

再说说KMP模式匹配。在KMP模式匹配的过程中,我们使用到了一个next函数(如果你高兴,也可以说这是一张next表)。next函数的作用是,当我们在匹配的过程中,发生了匹配失败的时候,可以将模式串向前滑动n个字符,从而省去了n次的比较操作。而具体的操作方法及说明,我在之前的博客中也有介绍,这里不再详细说明。

试想一下,如果我们要匹配一个文本文件d(举例文件的目的是为了说明,这个匹配字符串可能会是一个很长的字符串),使用Trie树的匹配方式,依然需要对d进行循环遍历,就像朴素模式匹配那样。Trie树减少的只是在Trie树中重合的部分,所以时间复杂会相当高。那么,KMP算法呢?对于KMP算法,我们要清楚一点。KMP算法是给模式串生成next函数,在多模式的情况下,我们需要生成很多的next函数,再对每个模式进行匹配。这显然也并不理想。

基于以上这两点,我们的AC算法诞生了。

5.1.原理

AC为了克服Trie树中无效匹配和KMP算法需要一个一个去匹配,设计了一种新的算法。算法中需要维护三个函数,分别是:

success:从一个状态成功转移到另一个状态(有时也叫goto表或是success表)。

failure:从一个状态按照普通流程会匹配失败,这时我们要通过failure函数来做状态跳转。

emits:命中一个模式串(也称为output表)。

从上面的状态转移图中就可以看出来,整个节点+实线就是success函数;而虚线就是failure函数;红色节点则是emits函数。

6.代码实现过程及说明

6.0.整体实现过程流程图

6.1.创建Trie树

其实AC自动机是建立在Trie的基础之上的,从上面的状态转移图中就可以获得这一信息。而在AC算法的3个函数中的success函数就是一种Trie树。

[java] view plain copy

  1. /**
  2. * 构造一棵trie树
  3. *
  4. * @param trieConfig
  5. */
  6. public Trie(TrieConfig trieConfig) {
  7. this(trieConfig, true);
  8. }
  9. public Trie(TrieConfig trieConfig, boolean ascii) {
  10. this.trieConfig = trieConfig;
  11. if (ascii) {
  12. this.rootState = new AsciiState();
  13. else {
  14. this.rootState = new UnicodeState();
  15. }
  16. }

6.2.success表的创建

从上面我们知道,success函数的功能就是构建一个棵Trie树。关键是如何构建,因为这个Trie树的构建和我们之前说的那并不完全相同。

在AC算法中,我们把Trie树中的节点就直接称为状态(State).在创建状态转移表的过程中,则是利用了递推的思想。我们在添加字典的过程中,其实是去计算当前字符对应的下一下状态。详细过程,请参见如下代码:

[java] view plain copy

  1. /**
  2. * 转移到下一个状态
  3. *
  4. * @param character       希望按此字符转移
  5. * @param ignoreRootState 是否忽略根节点,如果是根节点自己调用则应该是true,否则为false
  6. * @return 转移结果
  7. */
  8. private State nextState(Character character, boolean ignoreRootState) {
  9. State nextState = this.success.get(character);
  10. if (!ignoreRootState && nextState == null && this.rootState != null) {
  11. nextState = this.rootState;
  12. }
  13. return nextState;
  14. }
  15. @Override
  16. public State nextStateIgnoreRootState(Character character) {
  17. return nextState(character, true);
  18. }
  19. @Override
  20. public State addState(Character character) {
  21. State nextState = nextStateIgnoreRootState(character);
  22. if (nextState == null) {
  23. nextState = new UnicodeState(this.depth + 1);
  24. this.success.put(character, nextState);
  25. }
  26. return nextState;
  27. }

6.3.failure表的创建

failure表的创建是一个广度优先搜索的过程。在这个过程中,我们通过不断遍历状态Trie树。详细编码过程如下:

[java] view plain copy

  1. /**
  2. * 建立failure表
  3. */
  4. private void constructFailureStates() {
  5. Queue<State> queue = new LinkedBlockingDeque<State>();
  6. // 第一步,将深度为1的节点的failure设为根节点
  7. for (State depthOneState : this.rootState.getStates()) {
  8. depthOneState.setFailure(this.rootState);
  9. queue.add(depthOneState);
  10. }
  11. this.failureStatesConstructed = true;
  12. // 第二步,为深度 > 1 的节点建立failure表,这是一个bfs
  13. while (!queue.isEmpty()) {
  14. State currentState = queue.remove();
  15. for (Character transition : currentState.getTransitions()) {
  16. State targetState = currentState.nextState(transition);
  17. queue.add(targetState);
  18. State traceFailureState = currentState.failure();
  19. while (traceFailureState.nextState(transition) == null) {
  20. traceFailureState = traceFailureState.failure();
  21. }
  22. State newFailureState = traceFailureState.nextState(transition);
  23. targetState.setFailure(newFailureState);
  24. targetState.addEmit(newFailureState.emit());
  25. }
  26. }
  27. }

6.4.emits命中(output表的创建)

关于output表的创建,其实跟Trie树中的结束结点标志很类似。都是在模式串的末尾对状态进行修改的过程。而output表则是在状态节点对象中以组合的方式来体现。

[java] view plain copy

  1. /**
  2. * 添加一个模式串
  3. *
  4. * @param keyword
  5. */
  6. public void addKeyword(String keyword) {
  7. ...
  8. currentState.addEmit(keyword);
  9. }
  10. /**
  11. * 添加一个匹配到的模式串(这个状态对应着这个模式串)
  12. *
  13. * @param keyword
  14. */
  15. public void addEmit(String keyword) {
  16. if (this.emits == null) {
  17. this.emits = new TreeSet<String>();
  18. }
  19. this.emits.add(keyword);
  20. }

7.GitHub 源码

https://github.com/William-Hai/J-AhoCorasick

理解Aho-Corasick自动机算法相关推荐

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

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

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

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

  3. 深入理解Aho-Corasick自动机算法

    0.前言   我总是对那些具有状态转移过程的算法,心怀敬意.   例如:递归.递推.动规.DAT 以及现在要说的 AC 自动机算法.   数学真是优美!                         ...

  4. KMP算法、AC自动机算法的原理介绍以及Python实现

    KMP算法 要弄懂AC自动机算法,首先弄清楚KMP算法. 这篇文章讲的很好: http://www.ruanyifeng.com/blog/2013/05/Knuth%E2%80%93Morris%E ...

  5. AC自动机算法详解以及Java代码实现

    详细介绍了AC自动机算法详解以及Java代码实现. 文章目录 1 概念和原理 2 节点定义 3 构建Trie前缀树 3.1 案例演示 4 构建fail失配指针 4.1 案例演示 5 匹配文本 5.1 ...

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

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

  7. 极限定律 My Algorithm Space AC自动机算法详解

    转载自:http://www.cppblog.com/mythit/archive/2009/04/21/80633.html 首先简要介绍一下AC自动机:Aho-Corasick automatio ...

  8. AC自动机算法及模板

    AC自动机算法及模板 2016-05-08 18:58 226人阅读 评论(0) 收藏 举报  分类: AC自动机(1)  版权声明:本文为博主原创文章,未经博主允许不得转载. 目录(?)[+] 关于 ...

  9. 【机器学习】通俗的元胞自动机算法解析和应用

    [机器学习]通俗的元胞自动机算法解析和应用 文章目录 1 元胞自动机的定义 2 元胞自动机的组成 3 元胞自动机的特征 4 Python实现元胞自动机(生命游戏) 5 总结 6 Github(华盛顿州 ...

最新文章

  1. C语言控制结构程序设计,第3讲 C语言程序的基本控制结构_C语言程序设计(上)_pps_大学课件预览_高等教育资讯网...
  2. java从数组查找指定整数_如何在Java中使用重复项查找整数数组中的K个缺失数字?...
  3. C语言 const 修饰变量 - C语言零基础入门教程
  4. ML《集成学习(一)Bagging 和 Random Forest》
  5. 韵达混合云深度解析:Docker助力大规模云上调度实践
  6. 高通平台Bring-up
  7. PHP什么架构,PHP是什么-PHP的架构及道理概述_后端开发
  8. js实现数字转换大写金额
  9. 软件的高可用性、可扩展性和高性能
  10. Linux三剑客之awk精讲
  11. 系统指定的路径不存在,怎么办
  12. 在Mac上安装Hadoop HA 高可
  13. 【OSATE学习笔记】AADL语法介绍(二)软件类构建详细介绍
  14. 在场景中增加固定自定义栏
  15. Dynamic Routing Between Capsule中难点理解
  16. 为什么程序员不那么爱说话
  17. CSS设计指南---页面布局
  18. ST意法芯片:全球领先的半导体技术,你不能不知道!
  19. python性能测试书籍_Python的性能测试神器-Locust
  20. TCP/IP实现(十) 协议控制块

热门文章

  1. 游戏设计模式-原型模式
  2. 【图像去噪】基于matlab小波滤波(硬阙值+软阙值)+中值滤波图像去噪【含Matlab源码 462期】
  3. 【STM32学习】(21)STM32实现步进电机
  4. 五秒去除视频水印,这么简单的方法一定要收藏!
  5. 南大袁春风计算机系统基础(一)笔记
  6. XUPT 寒假算法集训第一周
  7. 打印机与电脑显示不连接到服务器,网络打印机无法连接怎么办?网络打印机设置步骤...
  8. 电子邮件格式怎么填写,创建一个标准的电子邮箱邮件格式怎么注册
  9. python画地图经纬度_如何用python画地图上的标注线?
  10. html调用手机NFC,NFC门禁模拟-教你用NFC手机模拟门禁卡