zoukankan      html  css  js  c++  java
  • 浅谈AC自动机

    引入

    我们发现(Trie)树可以进行多模式串匹配,(KMP)可以快速进行子串匹配,那么如果我们要进行多模式串子串匹配怎么办呢?这里我们就将介绍一个综合了(Trie)树和(KMP)的算法——(AC)自动机。

    简述

    上面已经提到了(AC)自动机就是(Trie)树和(KMP)的综合,如果还不会或者不熟悉这两种算法的话请移步初阶字符串算法

    我们首先要建出一棵(Trie)树来,和普通的(Trie)树一样的。

    代码如下:

    void insert(char *s){
        int p=0,len=strlen(s);
        for(int i=0;i<len;i++){
            int ch=s[i]-'a';
            if(!trie[p][ch])trie[p][ch]=++tot;
            p=trie[p][ch];
        }ed[p]++;
    }
    

    然后我们就需要和(KMP)一样构建失配指针,也就是(fail)指针。而与(KMP)算法不同的是(KMP)算法的(fail)指针是相同的前后缀,而(AC)自动机只需要相同后缀就可以了。

    那么我们应该如何构建呢?我们同样也是利用已经求得的(fail)指针来推导出当前节点的(fail)指针。我们采用(BFS)的方式来实现这个过程。我们设现在的节点为(x),它的父亲节点为(fa_x),它和它的父亲之间通过字符为(ch)的边连接,且深度小于(x)的所有节点的(fail)指针都已经求得。

    • 我们首先跳转到(fail[fa_x])节点
      • 如果(fail[fa_x])节点有一个子节点(y)是由该节点通过字符为(ch​)的边连接的
        • (x)(fail)指针指向(y),即(fail[x]=y)
      • 如果不存在这样的节点
        • 那么就跳转到(fail[fa_x])(fail)指针指向的节点,即(fail[fail[fa_x]]),然后重复上述过程
        • 如果一直跳转到根节点依然没有满足条件的节点的话,就让(x)(fail)指针指向根节点,即(fail[x]=root)

    上述过程便是(fail)指针的构建过程了。

    对于字符串集合({hers,his,she,i})构建出来的(fail)指针如下:

    代码如下:

    void make_fail(){
        queue<int >q;
        memset(fail,0,sizeof(fail));
        for(int i=0;i<26;i++)if(trie[0][i])q.push(trie[0][i]);
        while(!q.empty()){
            int x=q.front();q.pop();
            for(int i=0;i<26;i++){
                if(trie[x][i]){
                    fail[trie[x][i]]=trie[fail[x]][i];
                    q.push(trie[x][i]);
                }else trie[x][i]=trie[fail[x]][i];
            }
        }
    }
    

    我们注意到上面的构建(fail)指针的代码中trie[x][i]=trie[fail[x]][i]这句话,为什么直接将(fail[x])的子节点直接赋给(x)了呢?其实这行代码和上面两行的fail[trie[x][i]]=trie[fail[x]][i]共同做了类似于并查集的“路径压缩”的事情,也就是使得本身可能要跳转很多次的(fail)指针变成只需要跳转一次,这个可以感性理解一下。

    匹配函数的代码如下:

    int query(char *s){
        int p=0,ret=0,len=strlen(s);
        for(int i=0;i<len;i++){
            int ch=s[i]-'a';p=trie[p][ch];
            for(int j=p;j&&~ed[j];j=fail[j])ret+=ed[j],ed[j]=-1;
        }return ret;
    }
    

    (p)就是当前在(Trie)树上的节点,然后利用(fail)指针来找出所有匹配的模式串。但是我们发现一个问题,p=trie[p][ch]这行代码告诉我们(p)似乎是不断向后跳的,并没有像(KMP)一样跳到失配指针指向的节点。但其实并不然,我们看一下之前的构造(fail)指针的代码,我们发现,我们其实是对(Trie)树进行了改造的。所以,我们并不是不断向后跳的,我们同样的实现了跳(fail)指针的操作。

    以上便是(AC)自动机的构造失配指针和匹配过程。

    总代码如下:

    struct AC_automaton{
        int tot,ed[maxn],fail[maxn],trie[maxn][26];
        void insert(char *s){
            int p=0,len=strlen(s);
            for(int i=0;i<len;i++){
                int ch=s[i]-'a';
                if(!trie[p][ch])trie[p][ch]=++tot;
                p=trie[p][ch];
            }ed[p]++;
        }
        void make_fail(){
            queue<int >q;
            memset(fail,0,sizeof(fail));
            for(int i=0;i<26;i++)if(trie[0][i])q.push(trie[0][i]);
            while(!q.empty()){
                int x=q.front();q.pop();
                for(int i=0;i<26;i++){
                    if(trie[x][i]){
                        fail[trie[x][i]]=trie[fail[x]][i];
                        q.push(trie[x][i]);
                    }else trie[x][i]=trie[fail[x]][i];
                }
            }
        }
        int query(char *s){
            int p=0,ret=0,len=strlen(s);
            for(int i=0;i<len;i++){
                int ch=s[i]-'a';
                p=trie[p][ch];
                for(int j=p;j&&~ed[j];j=fail[j])ret+=ed[j],ed[j]=-1;
            }return ret;
        }
    }AC;
    
  • 相关阅读:
    POJ 3710 Christmas Game#经典图SG博弈
    POJ 2599 A funny game#树形SG(DFS实现)
    POJ 2425 A Chess Game#树形SG
    LeetCode Array Easy 122. Best Time to Buy and Sell Stock II
    LeetCode Array Easy121. Best Time to Buy and Sell Stock
    LeetCode Array Easy 119. Pascal's Triangle II
    LeetCode Array Easy 118. Pascal's Triangle
    LeetCode Array Easy 88. Merge Sorted Array
    ASP.NET MVC 学习笔记之 MVC + EF中的EO DTO ViewModel
    ASP.NET MVC 学习笔记之面向切面编程与过滤器
  • 原文地址:https://www.cnblogs.com/Luvwgyx/p/10279560.html
Copyright © 2011-2022 走看看