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自动机就没那么难了