AC算法(自学过程详细讲解,方便大家理解)

Aho-Corasick自动机算法,简称就叫做AC算法。网上看了各种各样的AC算法,其实都看的不太理解。最后还得领导亲自给我讲解了前因后果,才能真正理解了。刚刚从经历了懵逼到理解的过程,我得赶紧记录下我理解此算法的过程,相信很多刚刚看此算法的同学们,应该会有很多和我一样的问题。

首先,先讲述一下此算法的作用是什么,为什么要用AC算法,什么时候用?
AC算法主要是解决多字符串匹配问题,比如字符串ushers,作为长字符串。 多模式串:he/ she/ hers/ his ,作为匹配串。然后要解决的问题就是:在长字符串ushers中,是否包含多模式串?(也就是ushers中,是否存在he,是否存在she…等等)。
然后,为什么要用AC呢?对于解决多字符串匹配,传统暴力匹配其实也可以,不过时间复杂度太高,所以才学习AC呀,有AC肯定不用暴力匹配啦。(暴力匹配很简单,自己了解吧)
所以,相比较传统暴力匹配,AC的优点在哪里?1,AC巧妙的将字符匹配转换为了状态转移。2,扫描长串(即此例中ushers)时,只需要扫描一次。
1,将字符匹配转换为了状态转移。
说到状态转移,就需要先解释一下什么是AC自动机?什么是自动机?不理解自动机的话,肯定看不懂AC。
AC算法(自学过程详细讲解,方便大家理解)
首先,解释自动机:自动机一般存在几种状态,状态是什么?比如,开关有两种状态,筛子有六种状态。我们讲的自动机一般指的是有限自动机,即状态有限的自动机。比如图中的自动机,有A,B,C三种状态。那么,自动机就是在某些外界刺激下,能够改变状态的机器,在图中,A状态,通过0,3,6,9的刺激,能够回滚到本身状态,通过1,4,7刺激可以到达B状态。
理解了自动机的原理,下面就讲解初始的问题,AC将字符匹配转换为了状态转移。即AC通过构建自动机,将字串匹配的过程,转换为了在状态机之间状态的跳转来表示字符匹配过程。先理解了自动机的运行原理,下面再讲解具体怎样转移状态的。
2,扫描长串(即此例中ushers)时,只需要扫描一次。
用AC算法的话,长字符串只需要遍历一遍,即ushers只需要在自动机里面跑一遍就够了,很厉害有木有?时间复杂度降低了太多啦。

AC算法思想:用多模式串建立一个树形有限状态机,即先以多模式串(短字符串)为基础创建自动机。以主串(长字符串)作为该自动机的输入,也是就把长字符串在状态机里面跑一遍,使状态机进行各种状态之间的转换,当到自动机达某些特定的状态时,用状态表示字符是否匹配,即某些字符串是否匹配上了。
AC算法(自学过程详细讲解,方便大家理解)
状态转移函数:
该图就是一个使用多模式串(he/ she/ hers/ his )创建的自动机,讲解一下自动机创建过程:首先,圆圈表示状态,初始状态都为0,然后,读取第一个字符串he,0状态受到第一个字符h的刺激改变为1状态,然后,在1状态的基础上,he的第二个字符e进行刺激,又使状态改变,从1状态改变为2状态,此时,第一个字符串读取完毕,将末尾字符到达的状态,2状态标记为output输出状态(图中红色)。然后,读取下一个多模式串,she字符串,首先判断,s刺激是否已经存在?不存在,则从0状态受s刺激,转换为3状态,然后依次she刺激创建状态,标记最后一个状态5为output状态。下一个hers,稍微有点特殊,当第一个刺激h已经存在时,不需要创建新状态,直接到达状态1就行了,只有在当前状态下,受到的刺激是新刺激时,才会创建新状态。然后依次为所有字符串创建自动机路径。图中实线即为状态转移路径,即在每个状态下,受到相应的刺激,就会沿着实现改变状态机的状态。实线即为状态转移函数。
匹配过程:
我觉得匹配过程需要在失效函数之前讲解,这很重要,因为从我个人理解来讲,只要先明白了匹配过程,才能明白失效函数到底什么作用。
我们例子中的长串,ushers,放到自动机中运行,首先,第一个字符u,0状态不存在u的刺激,所以返回自身状态,还是0。然后下一个字符s,存在刺激s,所以状态机状态变为3,然后,再下一个刺激是h,存在刺激h,状态机状态改变为4,再下一个刺激是e,存在刺激e,所以状态机状态改变为5。此时,因为5被标记为output状态,所以,此时,多模式串中she字符串匹配成功。然后,再下一个刺激是r,然而此时,和之前不同的状态出现了,此时状态机中,在当前状态下,并不存在刺激r,那怎么办呢?难道状态机就此停住了?肯定不会啊,那状态机应该怎么跳转?跳转为哪个状态?这就用到失效函数了。
失效函数:
比如刚才上面的例子,在5状态时,此时受到r的刺激,但是5状态本身,并不存在刺激为r的状态转移函数,此时,失效函数就派上用场了。失效函数,相比于状态转移函数,是在失配的状态下调用的函数。状态转移函数,告诉我们受到刺激时,然后转移到某个状态,但是失配时呢?都失配了,匹配不上啦!那么多乱七八糟的状态,鬼知道该往哪转移?其实我刚开始理解AC的时候,也是主要被失效函数困扰。下面具体讲解一下失效函数的运作过程。
失效函数应该转移到的状态点为:从这个状态节点向上直到树根节点(状态0)所经历的输入字符,和从产生失效状态的那个状态节点向上所经历的输入字符串完全相同。而且这个状态节点,是所有具备这些条件的节点中深度最大的那个节点。如果不存在满足条件的状态节点,则失效函数为0。
这句话怎么理解呢?首先,转移到的点,到根节点的路径上所经历的字符,应该<=从失效点到根节点路径上所经历的字符,拿刚才的例子,失效点5到根节点(5->4->3->0)所经历的字符为:ehs 。那么,状态2到根节点(2->1->0)所经历的字符为:eh。所以,eh<=ehs,所以,失效函数应该指向状态2。这里有一个问题,比如,可能会同时出现好多个到根节点所经历的字符<=ehs,比如,假如有状态X到根节点的字符为ehs,ehs<=ehs。那么此时,失效函数应该指向状态X,而不是状态2,因为状态X到根节点的距离更深。有几个需要注意的地方,首先eh<=ehs,必须都是以e开头的,即<=在这里有一个前提,就是首字符必须相同,虽然hs<=ehs,但是首字符不相同,不匹配。其次,必须是到根节点的路径。当不存在失效函数可以匹配的结果时,即没有任何一个节点到根节点的路径<=失效节点到根节点的路径时,失效函数指向根节点,状态机转为0状态。

到这里,AC算法的基本思路已经讲完了,代码的话,自己实现试试嘛。我觉得对于AC算法,个人体会而言,最难理解的是自动机的概念和失效函数状态转移的理解。有问题可以留言探讨哦。