AC自动机入门

KMP算法可以解决单个模式串比较的问题,如果模式串有多个,那么KMP算法的效率就不尽如人意了。

一种可行的办法是AC自动机,解决多模式串匹配的问题。
AC自动机和KMP算法的思想是相同的,就是跳过不必要的移动,直接到失配的位置所对应处。只是AC自动机是以trie(字典树)为基础的。
同KMP算法类似,AC自动机的算法过程大致如下:

1.计算trie树中每个节点失配后直接指向的位置
2.主串匹配

不妨结合KMP算法的next数组考虑(由于trie树中有next这个名称,为避免重复,trie上用fail来表示失配后指向的位置,fail的作用和KMP算法中的next是相同的作用),fail同样应当表示“最长公共前后缀”,这样才能跳过不必要的移动。不同的是,KMP是线性处理,而AC自动机需要的是在trie树上。

不难发现,计算fail的方法如下:
在trie上从根节点开始BFS,根据BFS序来依次处理每一个节点(为啥不是DFS呢?因为既然是最长公共前后缀,那么每个节点的fail指向的节点深度应该小于自己,BFS的顺序就可以保证)。对于节点x,从它的父亲节点的fail节点开始,与KMP类似,一直往fail位置跳,直到当前节点的字符与x的字符相同。不难发现,这就是节点x所代表的前缀的“最长公共前后缀”。特别的,如果没有找到fail,就把节点x的fail赋成根节点(这样就不会造成影响)。

void create_fail () {
    int s, t, i ;
    node *x ;
    h->fail = NULL ; // h means root
    Q[1] = h ;
    for ( s = 1, t = 1 ; s <= t ; s ++ ) {
        x = Q[s] ;
        for ( i = 0 ; i < 30 ; i ++ ) {
            if ( x->nxt[i] ) {
                for ( p = x->fail ; p && !p->nxt[i] ; p = p->fail ) ;
                x->nxt[i]->fail = p? p->nxt[i] : h ;
                Q[++t] = x->nxt[i] ;
            }
        }
    }
}

主串匹配的时候,与KMP类似,就一直往fail跳,直到找到匹配当前字符的位置,如果没有就会随fail回到根节点重新来。

int Match() {
    int i, j, ans = 0 ;
    char c ;
    q = h ;
    for ( i = 1 ; i <= m ; i ++ ) {
        c = pat[i] ;
        while ( q && !q->nxt[ c-'a' ] ) q = q->fail ;
        q = q? q->nxt[c-'a'] : h ;
        for ( p = q ; p ; p = p->fail ) {
            ans += p->tim ; 
            p->tim = 0 ;
        }
    }
    return ans ;
}

这里用HDU2222模板给出完整代码

#include 
#include 
#include 
#include 
#include 
using namespace std ;
const int maxn = 60 ;
char s[10010][maxn], pat[1000010] ;
struct node {
    node* nxt[30] ;
    node *fail, *fa ;
    int tim ;
    char ch ;
    node() {
        for ( int i = 0 ; i < 30 ; i ++ ) 
            nxt[i] = NULL ;
        ch = '\0' ;
        tim = 0 ;
        fa = fail = NULL ;
    }
} *h, *p, *q ;

int n, m, tot, len[10010] ;

void create_trie ( int x ) {
    p = h ;
    int c ;
    for ( int i = 1 ; i <= len[x] ; i ++ ) {
        c = s[x][i]-'a' ;
        if ( p->nxt[c] )
            p = p->nxt[c] ;
        else {
            ( p->nxt[c] = new node ) -> fa = p ;
            p = p->nxt[c] ;
            p->ch = s[x][i] ;
        }
    }
    p->tim ++ ;
}

node *Q[100010] ;
void create_fail () {
    int s, t, i ;
    node *x ;
    h->fail = NULL ;
    Q[1] = h ;
    for ( s = 1, t = 1 ; s <= t ; s ++ ) {
        x = Q[s] ;
        //printf ( "BFS Q[%d] : %c\n", s-1, x->ch ) ;
        for ( i = 0 ; i < 30 ; i ++ ) {
            if ( x->nxt[i] ) {
                for ( p = x->fail ; p && !p->nxt[i] ; p = p->fail ) ;
                x->nxt[i]->fail = p? p->nxt[i] : h ;
        //      printf ( "push : %c\n", x->nxt[i]->ch ) ;
                Q[++t] = x->nxt[i] ;
            }
        }
    }
}

int Match() {
    int i, j, ans = 0 ;
    char c ;
    q = h ;
    for ( i = 1 ; i <= m ; i ++ ) {
        c = pat[i] ;
        while ( q && !q->nxt[ c-'a' ] ) q = q->fail ;
        q = q? q->nxt[c-'a'] : h ;
        for ( p = q ; p ; p = p->fail ) {
            ans += p->tim ; 
            p->tim = 0 ;
        }
    }
    return ans ;
}

void check ( node* now ) {
    printf ( "%c fa:%c fail:%c tim:%d\n", now->ch, now->fa?now->fa->ch:'N', now->fail?now->fail->ch:'N', now->tim ) ;
    for ( int i = 0 ; i < 30 ; i ++ ) 
        if ( now->nxt[i] ) 
            check(now->nxt[i]) ;
}

int main() {
    int i, j, k, _ ;
    scanf ( "%d", &_ ) ;
    while ( _-- ) {
        scanf ( "%d", &n ) ;
        h = new node ;
        q = h ;
        for ( i = 1 ; i <= n ; i ++ ) {
            scanf ( "%s", s[i]+1 ) ;
            len[i] = strlen( s[i]+1 ) ;
        }
        for ( i = 1 ; i <= n ; i ++ ) 
            create_trie(i) ;

        create_fail() ;
        scanf ( "%s", pat+1 ) ;
        m = strlen(pat+1) ;
        printf ( "%d\n", Match() ) ;
    }
    return 0 ;
}

吐槽:幸好我先学了KMP,这样AC自动机就没那么难了

你可能感兴趣的:(算法,字符串,AC自动机)