zoukankan      html  css  js  c++  java
  • 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自动机板子题

  • 相关阅读:
    Hdu 1257 最少拦截系统
    Hdu 1404 Digital Deletions
    Hdu 1079 Calendar Game
    Hdu 1158 Employment Planning(DP)
    Hdu 1116 Play on Words
    Hdu 1258 Sum It Up
    Hdu 1175 连连看(DFS)
    Hdu 3635 Dragon Balls (并查集)
    Hdu 1829 A Bug's Life
    Hdu 1181 变形课
  • 原文地址:https://www.cnblogs.com/kongbursi-2292702937/p/12031259.html
Copyright © 2011-2022 走看看