AC自动机算法 && 例题

参考链接:

https://blog.csdn.net/bestsort/article/details/82947639#commentBox

https://blog.csdn.net/niushuai666/article/details/7002823

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

ac自动机时间复杂度:
假设有N个模式串,平均长度为L;文章长度为M。 建立Trie树:O(N*L) 建立fail指针:O(N*L) 模式匹配:O(M*L) 所以,总时间复杂度为:O( (N+M)*L )。

AC自动机构造:

1、首先用n个单词构造一颗字典树

2、为树上的每一个节点设一个失败指针(代码中用fail[]数组来表示),这个失败指针和KMP算法有点类似。用KMP算法来找的时候如果匹配失败会通过next数组跳到下一个位置进行匹配。失败指针也是这个用途,它就是此次匹配失败后跳到下一个位置进行下一次匹配

3、扫描主串进行匹配

具体AC自动机构造:

一、Trie
首先我们需要建立一棵Trie。但是这棵Trie不是普通的Trie,而是带有一些特殊的性质。
首先会有3个重要的指针,分别为p, p->fail, temp
1.指针p,指向当前匹配的字符。若p指向root,表示当前匹配的字符序列为空。(root是Trie入口,没有实际含义)。
2.指针p->fail,p的失败指针,指向与字符p相同的结点,若没有,则指向root。
3.指针temp,测试指针(自己命名的,容易理解!~),在建立fail指针时有寻找与p字符匹配的结点的作用,在扫描时作用最大,也最不好理解。

对于Trie树中的一个节点,对应一个序列s[1...m]。此时,p指向字符s[m]。若在下一个字符处失配,即p->next[s[m+1]] == NULL,则由失配指针跳到另一个节点(p->fail)处,该节点对应的序列为s[i...m]。若继续失配,则序列依次跳转直到序列为空或出现匹配。在此过程中,p的值一直在变化,但是p对应节点的字符没有发生变化。在此过程中,我们观察可知,最终求得得序列s则为最长公共后缀。另外,由于这个序列是从root开始到某一节点,则说明这个序列有可能是某些序列的前缀。
再次讨论p指针转移的意义。如果p指针在某一字符s[m+1]处失配(即p->next[s[m+1]] == NULL),则说明没有单词s[1...m+1]存在。此时,如果p的失配指针指向root,则说明当前序列的任意后缀不会是某个单词的前缀。如果p的失配指针不指向root,则说明序列s[i...m]是某一单词的前缀,于是跳转到p的失配指针,以s[i...m]为前缀继续匹配s[m+1]。
对于已经得到的序列s[1...m],由于s[i...m]可能是某单词的后缀,s[1...j]可能是某单词的前缀,所以s[1...m]中可能会出现单词。此时,p指向已匹配的字符,不能动。于是,令temp = p,然后依次测试s[1...m], s[i...m]是否是单词。

fail指针构造:
首先给定模式串"ash","shex","bcd","sha",然后我们根据模式串建立如下trie树:

 然后我们再了解下一步:
ac自动机,就是在tire树的基础上,增加一个fail指针,如果当前点匹配失败,则将指针转移到fail指针指向的地方,这样就不用回溯,而可以路匹配下去了.(当前模式串后缀和fail指针指向的模式串部分前缀相同,如abce和bcd,我们找到c发现下一个要找的不是e,就跳到bcd中的c处,看看此处的下一个字符(d)是不是应该找的那一个)
一般,fail指针的构建都是用bfs实现的
首先每个模式串的首字母(也就是根节点的直系子节点)肯定是指向根节点的

 现在第一层bfs遍历完了,开始第二层
(根节点为第0层)第二层a的子节点为s,但是我们还是要从a-z遍历,如果不存在这个子节点我们就让他指向根节点(如下图红色的a)

 当我们遍历到s的时候,由于它的父亲节点(即、a)的fail指针指向的节点(即、根)的直系子节点存在s这个节点,我们就让他的fail指针指向他父亲节点(a)的fail指针指向的那个节点()的具有相同字母的子节点(第一层的s),也就是这样(没错fail指针就是这样构造的

 按照相同规律构建第二层后,到了第三层的h点,还是按照上面的规则,我们找到h的父亲节点(s)fail指针指向的那个位置(第一层的s)然后指向它所指向的相同字母根->s->h的这个链的h节点,如下图

 完全构造好后的树

 然后匹配就很简单了,这里以ashe为例
我们先用ash匹配,到h了发现:诶这里ash是一个完整的模式串,好的ans++,然后找下一个e,可是ash后面没字母了啊,我们就跳到hfail指针指向的那个h继续找,还是没有e?再跳,结果当前的h指向的是根节点,又从根节点找,然而还是没有找到e,程序END

 扫描主串:

构造好Trie和失败指针后,我们就可以对主串进行扫描了。这个过程和KMP算法很类似,但是也有一定的区别,主要是因为AC自动机处理的是多串模式,需要防止遗漏某个单词,所以引入temp指针。

匹配过程分两种情况:(1)当前字符匹配,表示从当前节点沿着树边有一条路径可以到达目标字符,此时只需沿该路径走向下一个节点继续匹配即可,目标字符串指针移向下个字符继续匹配;(2)当前字符不匹配,则去当前节点失败指针所指向的字符继续匹配,匹配过程随着指针指向root结束。重复这2个过程中的任意一个,直到模式串走到结尾为止。

扫描代码:

 1 int len=strlen(s);
 2         int now=root;
 3         int res=0;
 4         for(int i=0;i<len;++i)
 5         {
 6             now=next[now][s[i]-'a']; //这个now就保证了我们找的肯定是这个s字符串中的一部分
 7             int temp=now; //这就是一直找,找到了就加1,找不到就通过失败指针看其他地方能不能找到
 8             while(temp!=root)  //尝试这个节点的失败指针能不能找到模式串
 9             {
10                 res+=ends[temp];  //因为我们要找出来s串中包含几种模式串,所以要有赋值0操作
11                 ends[temp]=0;
12                 temp=fail[temp];
13             }
14         }
15         return res;

例题:

Keywords Search HDU - 2222 AC自动机板子题

原文地址:https://www.cnblogs.com/kongbursi-2292702937/p/12031259.html