iis服务器助手广告广告
返回顶部
首页 > 资讯 > 精选 >java编程之AC自动机工作原理的示例分析
  • 408
分享到

java编程之AC自动机工作原理的示例分析

java 2023-05-30 21:05:52 408人浏览 独家记忆
摘要

这篇文章将为大家详细讲解有关java编程之AC自动机工作原理的示例分析,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。1.应用场景—多模字符串匹配我们现在考虑这样一个问题,在一个文本串text中,我们想找出

这篇文章将为大家详细讲解有关java编程之AC自动机工作原理的示例分析,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。

1.应用场景—多模字符串匹配

我们现在考虑这样一个问题,在一个文本串text中,我们想找出多个目标字符串target1,target2,……出现的次数和位置。例如:求出目标字符串集合{"nihao","hao","hs","hsr"}在给定文本"sdmfhsgnshejfgnihaofhsrnihao"中所有可能出现的位置。解决这个问题,我们一般的办法就是在文本串中对每个目标字符串单独查找,并记录下每次出现的位置。显然这样的方式能够解决问题,但是在文本串较大、目标字符串众多的时候效率比较低。为了提高效率,贝尔实验室于1975年发明著名的多模字符串匹配算法——AC自动机。AC自动机在实现上要依托于Trie树(也称字典树)并借鉴了KMP模式匹配算法的核心思想。实际上你可以把KMP算法看成每个节点都仅有一个孩子节点的AC自动机。

AC自动机用于多模匹配,所谓多模匹配,就是给定一个带匹配的字符串string,给定一个字典dictionary,dictionary中有多个字符串{ str1,str2, str3 … } 多模匹配就是要得到string字符串中出现了dictionary的哪些字符,且这些字符出现在了string中的哪个位置。

2.AC自动机及其运行原理

1初识AC自动机

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

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

java编程之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的最后一个字符。

2AC自动机的运行过程:

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

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

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

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

若失败:执行第4步。

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

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

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

java编程之AC自动机工作原理的示例分析

图中的实曲线表示了整个搜索过程中的当前结点指针的转移过程,结点旁的文字表示了当前结点下读取的文本串字符。比如初始时,当前指针指向根结点时,输入字符‘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自动机的方法与原理

1构造的基本方法

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

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

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

2)若队列不为空:

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

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)

若队列为空:结束

2通过一个例子来理解构造AC自动机的原理

每个结点fail指向的解决顺序是按照广度优先遍历的顺序完成的,或者说层序遍历的顺序进行的,也就是说我们是在解决当前结点的孩子结点fail的指向时,当前结点的fail指针一定已指向了正确的位置。

java编程之AC自动机工作原理的示例分析

为了说明问题,我们再次强调“每个结点的fail指针表示:由根结点到该结点所组成的字符序列的所有后缀和整个目标字符串集合(也就是整个Trie树)中的所有前缀两者中最长公共的部分”。

以上图所示为例,我们要解决结点x1的某个孩子结点y的fail的指向问题。已知x1.fail指向x2,依据x1结点的fail指针的含义,我们可知红色实线椭圆框内的字符序列必然相等,且表示了最长公共部分。依据y.fail的含义,如果x2的某个孩子结点和结点y表示的字符相等,那么y.fail就该指向它。

如果x2的孩子结点中不存在结点y表示的字符,这个时候该怎么办?由于x2.fail指向x3,根据x2.fail的含义,我们可知绿色方框内的字符序列必然相等。显然,如果x3的某个孩子结点和结点y表示的字符相等,那么y.fail就该指向它。

如果x3的孩子结点中不存在结点y表示的字符,我们可以依次重复这个步骤,直到xi结点的fail指向null,这时说明我们已经到了最顶层的根结点,这时,我们只需要让y.fail=root即可。

构造的过程的核心本质就是,已知当前结点的最长公共前缀的前提下,去确定孩子结点的最长公共前缀。这完全可以类比于KMP算法的next数组的求解过程。

2.1确定图中h结点fail指向的过程

现在我们假设我们要确定图中结点c的孩子结点h的fail指向。图中每个结点都应该有表示fail的虚线,但为了表示方便,按照本文约定的原则,所有指向根结点的fail虚线均未画出。

java编程之AC自动机工作原理的示例分析java编程之AC自动机工作原理的示例分析

左图表示h.fail确定之前, 右图表示h.fail确定之后

左图中,蓝色实线框住的结点的fail都已确定。现在我们应该怎样找到h.fail的正确指向?由于且结点c的fail已知(c结点为h结点的父结点),且指向了Trie树中所有前缀与字符序列‘a'‘b'‘c'的所有后缀(“bc”和“c”)的最长公共部分。现在我们要解决的问题是目标字符串集合的所有前缀中与字符序列‘a'‘b'‘c'‘h'的所有后缀的最长公共部分。显然c.fail指向的结点的孩子结点中存在结点h,那么h.fail就应该指向c.fail的孩子结点h,所以右图表示了h.fail确定后的情况。

2.2确定图中i.fail指向的过程

java编程之AC自动机工作原理的示例分析java编程之AC自动机工作原理的示例分析

左图表示i.fail确定之前, 右图表示i.fail确定之后

确定i.fail的指向时,显然h.fail(h指图中i的父结点的那个h)已指向了正确的位置。也就是说我们现在知道了目标字符串集合所有前缀中与字符序列‘a'‘b'‘c'‘h'的所有后缀在Trie树中的最长前缀是‘c'‘h'。显然从图中可知h.fail的孩子结点是没有i结点(这里h.fail只有一个孩子结点n)。字符序列‘c'‘h'的所有后缀在Trie树中的最长前缀可由h.fail的fail得到,而h.fail的fail指向root(依据本博客中画图的原则,这条fail虚线并未画出),root的孩子结点中存在表示字符i的结点,所以结果如右图所示。

在知道i.fail的情况下,大家可以尝试在纸上画出j.fail的指向,以加深AC自动机构造过程的理解。

4.AC自动机的java代码实现

package datastruct;import java.util.ArrayList;import java.util.HashMap;import java.util.LinkedList;import java.util.List;import java.util.Map.Entry;public class AhoCorasickAutomation {private static final int ASCII = 128;private node root;private List<String> target;private HashMap<String, List<Integer>> result;private static class Node{String str;Node[] table = new Node[ASCII];Node fail;public Boolean isWord(){return str != null;}}public AhoCorasickAutomation(List<String> target){root = new Node();this.target = target;buildTrieTree();build_AC_FromTrie();}private void buildTrieTree(){for (String targetStr : target){Node curr = root;for (int i = 0; i < targetStr.length(); i++){char ch = targetStr.charAt(i);if(curr.table[ch] == null){curr.table[ch] = new Node();}curr = curr.table[ch];}curr.str = targetStr;}}private void build_AC_FromTrie(){LinkedList<Node> queue = new LinkedList<Node>();for (Node x : root.table){if(x != null){x.fail = root;queue.addLast(x);}}while(!queue.isEmpty()){Node p = queue.removeFirst();for (int i = 0; i < p.table.length; i++){if(p.table[i] != null){queue.addLast(p.table[i]);Node failTo = p.fail;while(true){if(failTo == null){p.table[i].fail = root;break;}if(failTo.table[i] != null){p.table[i].fail = failTo.table[i];break;} else{failTo = failTo.fail;}}}}}}public HashMap<String, List<Integer>> find(String text){result = new HashMap<String, List<Integer>>();for (String s : target){result.put(s, new LinkedList<Integer>());}Node curr = root;int i = 0;while(i < text.length()){char ch = text.charAt(i);if(curr.table[ch] != null){curr = curr.table[ch];if(curr.isWord()){result.get(curr.str).add(i - curr.str.length()+1);}if(curr.fail != null && curr.fail.isWord()){result.get(curr.fail.str).add(i - curr.fail.str.length()+1);}i++;} else{curr = curr.fail;if(curr == null){curr = root;i++;}}}return result;}public static void main(String[] args){List<String> target = new ArrayList<String>();target.add("abcdef");target.add("abhab");target.add("bcd");target.add("cde");target.add("cdfkcdf");String text = "bcabcdebcedfabcdefababkabhabk";AhoCorasickAutomation aca = new AhoCorasickAutomation(target);HashMap<String, List<Integer>> result = aca.find(text);System.out.println(text);for (Entry<String, List<Integer>> entry : result.entrySet()){System.out.println(entry.geTKEy()+" : " + entry.getValue());}}}

运行结果如下,从结果中我们可以看出文本串中bcd出现了二次,分别是文本串下标为3和下标为13的位置,……。

bcabcdebcedfabcdefababkabhabkbcd : [3, 13]cdfkcdf : []cde : [4, 14]abcdef : [12]abhab : [23]

关于“java编程之AC自动机工作原理的示例分析”这篇文章就分享到这里了,希望以上内容可以对大家有一定的帮助,使各位可以学到更多知识,如果觉得文章不错,请把它分享出去让更多的人看到。

--结束END--

本文标题: java编程之AC自动机工作原理的示例分析

本文链接: https://www.lsjlt.com/news/221222.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • java编程之AC自动机工作原理的示例分析
    这篇文章将为大家详细讲解有关java编程之AC自动机工作原理的示例分析,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。1.应用场景—多模字符串匹配我们现在考虑这样一个问题,在一个文本串text中,我们想找出...
    99+
    2023-05-30
    java
  • Java注解机制之Spring自动装配实现原理的示例分析
    小编给大家分享一下Java注解机制之Spring自动装配实现原理的示例分析,希望大家阅读完这篇文章之后都有所收获,下面让我们一起去探讨吧! Java中使用注解的情况主要在SpringMVC(Spring Boot等),注解实际上相...
    99+
    2023-05-31
    java spring
  • Python自动化之批量处理工作簿和工作表的示例分析
    这篇文章主要介绍Python自动化之批量处理工作簿和工作表的示例分析,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!一、批量新建并保存工作簿import xlwings as xw&nbs...
    99+
    2023-06-15
  • java并发编程工具类JUC之LinkedBlockingQueue链表队列的示例分析
    小编给大家分享一下java并发编程工具类JUC之LinkedBlockingQueue链表队列的示例分析,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!java.u...
    99+
    2023-06-15
  • mybatis-plus雪花算法自动生成机器id原理的示例分析
    这篇文章主要介绍了mybatis-plus雪花算法自动生成机器id原理的示例分析,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。1、雪花算法原理  &nbs...
    99+
    2023-06-15
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作