AC自动机(算法介绍)
前言:
ac自动机能帮你自动通过ac题目(才怪),其实所谓的ac自动机他并不能帮你自动ac,而是一种多模式串的匹配算法.相较于kmp算法在运行多模式串的匹配时只需一次遍历即可,而kmp要针对不同的子序列对母序列进行多次遍历.
简介:
ac自动机是通过预处理所要查找的所有子序列并建立一颗字典树,通过遍历母序列时查找字典树进行操作.
例如图上所示.
当我们建立好树后对母串T进行遍历,此时我们的目的是找到子串P中的单词并统计他们出现的位置,当然我们也可以通过使用KMP算法对母串T进行多次遍历,但不如使用ac自动机算法对母串T进行多模式匹配更能节省时间,本算法的意义也就在这个地方.
关于KMP算法介绍于:kmp算法小结._kilig_CSM的博客-CSDN博客.
tree_树:
假设我们要查找的母串和子串如上,首先我们通过对子串进行预处理来建立一个树如上(文章下一部分会具体讲解怎样去进行预处理从而建树),然后我们对母串进行遍历,可以发现第一个字母为a,但我们的树的起点root并未连接a,所以下次查找节点还是从root节点开始查找,然后母串查找下一个字符h,可以发现root有一个h的子节点,所以下一个节点从h开始查,然后母串再次查找下一个字符i,可以找到h下边连接有i节点,所以下次查找从i节点开始查找,重复上述过程我们发现此次查找结束后his构成子串P里边中的单词,那么记一录信息.(在图中我们以带有圆环的节点表示能查找到完整子序列单词的节点)
此时我们继续往下查找,母序列该查找s后边的字符h,而在树中所应查找的位置变为图中所示的s节点(而不是root节点),具体原因要归功于我们的fail指针.下一步我们就来介绍一下fail指针的作用及特性.
fail指针:
fail指针是ac自动机的核心,也就是图中的虚线.
fail指针的意义:
如果fail指针从i指向j,那么说明word[j]是word[i]的最长后缀.
word的含义我们用一个例子来说明,比如所从root出发到节点6,那么word[6]就是sh,word[8]就是his.(图中绿色单词即为所要查找的子序列)
word[j]是word[i]的最长后缀这句话的含义:
举个例子,fail指针从word[6]指向word[2],而word[6]是sh,word[2]是h,那么h就是sh的最长后缀.
若fail指针从word[9]指向word[4],而word[8]是she,word[4]是he,那么he就是she的最长后缀.
若fail指针从word[10]指向word[3],而word[10]是hers,word[3]是s,那么s就是hers的最长后缀.
当然hers的后缀可以是ers也可以是rs或s,但是我们想要查找到的子序列中没有e或r开头的只有s开头的,所以最长后缀为s.
当然图中很多fail指针最终指向root,在这里root的意义就是空,代表什么都没有,即最长后缀为空.
我们对那些完整的单词节点作出标记,列如4号节点代表所需查找的单词he,8号节点代表所需查找的单词his,而9号节点就比较特殊---9号节点记录了两个单词she和he,之所以记录两个单词是因为she的最长后缀为he.
下边我们对着图中的序列进行一遍遍历,首先我们查找第一个字符a,发现从root节点开始并没有子节点为a的节点,所以下次查找的时候还从root节点开始查找.
然后再如上图从root查找h,i,s.可查找到单词his.找到单词his后有fail指针由8指向3.
是因为s是his的最长后缀,接着从3节点查找是否有赋值为h的子节点.
如图所示很明显我们可以找到h节点(即6号节点),然后从6号节点出发查找母序列中的下一个字符e.
按照规则重复上述过程最终查找路径如上图所示,可以从母序列中查找到his,she,he,hers等单词.
因此fail指针的意义就是这里,去通过最长后缀去直接查询母串的下一个字符,而不是去回溯到此次查询树的初始字母的下一个字母。
fail指针的构建以及查找过程:
在构建字典树的时候由于在3号节点可得到完整单词he,此时我们大可不必用bool类型去判断,我们可以用vecter容器去储存该单词的长度,例如3号节点存入he的长度2.(当1,2等不构成完整单词的节点存储exist时此时使exist为空,表示不构成任何单词.)
依次重复构建树如下图:
接着构建fail指针:
首先重申一遍fail指针的意义:若fail指针从i号节点指向j号节点,那么代表word[j]是word[i]的最长后缀,(即后者节点构成的字符串时前者的最长后缀.)
然后通过bfs遍历(层次遍历)字典树,在遍历过程中使得每个节点都有一个指针指向另一个节,使后者构成前者的最长后缀.(该指针具有唯一性.)
关于bfs遍历介绍于:bfs小结._kilig_CSM的博客-CSDN博客.
例如:字典树的第二层节点,即和root直接相连的节点,他们都代表单个字符,可以很明显的知道他们身为单个字符没有最长后缀,因此构建fail指针使他们分别都指向root节点.
然后我们遍历到下一层的3号节点,我们可以判断he的最长后缀,但我们也可以有一个更加简单的方法,即我们可以设一个名为fafail的指针变量,即代表该节点的父节点的fail指针.比如3号节点的fafail就是从2号节点指向root节点.此时我们只需判断root节点有没有一个字符为e的子节点即可.因为root不存在指向e的节点所以3号节点的fai指针也指向root节点.
我们重复上述过程使得9号节点的fail指针指向root节点,但当为5号节点构建fail指针时发现重复上述过程时root节点拥有字符为5号节点的父节点指向其的h字符的子节点.即4--5,和root--2都为h字符.所以5号节点的fail指针就由5号节点指向2号节点.
重复上述过程当我们建立到6号节点时fail指针指向了3号节点.有一个很关键的信息是6号节点fail指针的目标节点3号节点代表了一个完整的单词,所以我们要在6号节点上追加一个信息[2],代表不仅she的后三个字符是一个完整单词而且后两个字符也构成一个完整的单词.
重复上述过程构建fail指针如下:
然后我们来查找母串ahishers中的子串单词,过程如下.
1.首先从root节点开始查找.
2.按照之前的查找方法依次找到his找到s后通过10号节点的信息可以得知his的后三个字符构成一个单词his.
3.接着通过10号节点的fail指针传递至4号节点去查找是否有h字符子节点.
4.然后我们依次查找到6号节点时根据6号节点存储的信息可知word[6]:she的倒数前三个和倒数前两个字符分别构成目标单词she和he.
5.找到6号节点之后按照顺序查找到3,7,8号节点.可以发现8号节点内存有信息拥有目标单词hers.
还有一个疑问就是我们查找时为什么不把he的判定信息放在3号节点而是存放在6号节点,因为当我们存放在6号节点时我们可以直接找出两个单词,然后通过fail指针到达3号节点时其实并没有读取3号节点存储的单词判定信息,假设我们不把判定信息存在6号节点上的话把这种行为扩展到全局,我们每次查询fail指针指向的目标节点后都要再查询一遍目标节点的判定信息,会浪费一些时间.
总结:
ac自动机算法的终点和kmp算法一样都是对子串的预处理.
其关键在于字典树和fail指针的构建.