AC自动机 trie

来源:互联网 发布:为知笔记安卓导出 编辑:程序博客网 时间:2024/05/16 04:33

AC 自动机就是在 trie 上做 KMP ,先构造所有字符串的一个 trie ,再添加失败边(failure links),失败边跟 KMP 里的“失败函数”是一样的道理。

trie树实际上是一个DFA(Deterministic finite automaton),通常用转移矩阵表示。行表示状态,列表示输入字符,(行, 列)位置表示转移状态。这种方式的查询效率很高,但由于稀疏的现象严重,空间利用效率很

低。也可以采用压缩的存储方式即链表来表示状态转移,但由于要线性查询,会造成效率低下。

这是一个统计词频的trie树实例(来自wiki)

#include <stdio.h>#include <stdlib.h>#include <string.h> #define TREE_WIDTH 256 #define WORDLENMAX 128 struct trie_node_st {        int count;        struct trie_node_st *next[TREE_WIDTH];}; static struct trie_node_st root={0, {NULL}}; static char *spaces=" \t\n/.\"\'()"; static intinsert(const char *word){        int i;        struct trie_node_st *curr, *newnode;         if (word[0]=='\0') {                return 0;        }        curr = &root;        for (i=0; ; ++i) {                if (word[i] == '\0') {                        break;                }                if (curr->next[ word[i] ] == NULL) {                        newnode=(struct trie_node_st*)malloc(sizeof(struct trie_node_st));                        memset(newnode, 0, sizeof(struct trie_node_st));                        curr->next[ word[i] ] = newnode;                }                 curr = curr->next[ word[i] ];        }        curr->count ++;         return 0;} static voidprintword(const char *str, int n){        printf("%s\t%d\n", str, n);} static intdo_travel(struct trie_node_st *rootp){        static char worddump[WORDLENMAX+1];        static int pos=0;        int i;         if (rootp == NULL) {                return 0;        }        if (rootp->count) {                worddump[pos]='\0';                printword(worddump, rootp->count);        }        for (i=0;i<TREE_WIDTH;++i) {                worddump[pos++]=i;                do_travel(rootp->next[i]);                pos--;        }        return 0;} intmain(void){        char *linebuf=NULL, *line, *word;        size_t bufsize=0;        int ret;         while (1) {                ret=getline(&linebuf, &bufsize, stdin);                if (ret==-1) {                        break;                }                line=linebuf;                while (1) {                        word = strsep(&line, spaces);                        if (word==NULL) {                                break;                        }                        if (word[0]=='\0') {                                continue;                        }                        insert(word);                }        } /* free(linebuf); */         do_travel(&root);         exit(0);}

构造 AC 自动机

添加失败边的算法:对每一个节点,找出它的最长的在 trie 中出现过的后缀。比如对 she ,先找 he ,再找 e ,如果都没有则失败到 root 。

  但这种算法效率比较低,没有充分利用已经计算出来的信息。网上流传的是另外一种算法:比如要找 she 的失败边,可以假定其父节点,sh 的失败边已经求出,沿着父节

点的失败边走,如果某个节点有 e 的子节点,则把失败边指向该子节点。比如 she 的父节点 sh ,先走到 h ,假如 h 没有 e 的子节点的话,就继续检查 h 的失败边,即 

root 。如果最后到 root 都没有,就把失败边置为 root 。


匹配

在 AC 自动机上匹配:从根开始,如果当前字符匹配,则移动指针。然后需要沿着失败边检查:看有没有哪个是结尾节点,是则匹配成功(网上有些代码是只有当前整个串匹配

成功才找,但实际上只要当前字符是一样的就要找)。比如如果用上面的 AC 自动机匹配 she ,匹配到 she 的时候,she 这个单词匹配成功,还要沿失败边检查,发现 he

 和 e 也匹配成功。如果当前字符不匹配,则沿着失败边继续找,如果都没有匹配,则回到 root 。

 1 const int kind = 26
 2 struct node{  
 3     node *fail;       //失败指针
 4     node *next[kind]; //Tire每个节点的个子节点(最多个字母)
 5     int count;        //是否为该单词的最后一个节点
 6     node(){           //构造函数初始化
 7         fail=NULL; 
 8         count=0
 9         memset(next,NULL,sizeof(next)); 
10     } 
11 }*q[500001];          //队列,方便用于bfs构造失败指针
12 char keyword[51];     //输入的单词
13 char str[1000001];    //模式串
14 int head,tail;        //队列的头尾指针


 1 void insert(char *str,node *root){ 
 2     node *p=root; 
 3     int i=0,index;  
 4     while(str[i]){ 
 5         index=str[i]-'a'
 6         if(p->next[index]==NULL) p->next[index]=new node();  
 7         p=p->next[index];
 8         i++;
 9     } 
10     p->count++;     //在单词的最后一个节点count+1,代表一个单词
11 }


 1 void build_ac_automation(node *root){
 2     int i;
 3     root->fail=NULL; 
 4     q[head++]=root; 
 5     while(head!=tail){ 
 6         node *temp=q[tail++]; 
 7         node *p=NULL; 
 8         for(i=0;i<26;i++){ 
 9             if(temp->next[i]!=NULL){ 
10                 if(temp==root) temp->next[i]->fail=root;                 
11                 else
12                     p=temp->fail; 
13                     while(p!=NULL){  
14                         if(p->next[i]!=NULL){ 
15                             temp->next[i]->fail=p->next[i]; 
16                             break
17                         } 
18                         p=p->fail; 
19                     } 
20                     if(p==NULL) temp->next[i]->fail=root; 
21                 } 
22                 q[head++]=temp->next[i];  
23             } 
24         }   
25     } 
26 }


 1 int query(node *root){ 
 2     int i=0,cnt=0,index,len=strlen(str); 
 3     node *p=root;  
 4     while(str[i]){  
 5         index=str[i]-'a';  
 6         while(p->next[index]==NULL && p!=root) p=p->fail; 
 7         p=p->next[index]; 
 8         p=(p==NULL)?root:p; 
 9         node *temp=p; 
10         while(temp!=root && temp->count!=-1){ 
11             cnt+=temp->count; 
12             temp->count=-1
13             temp=temp->fail; 
14         } 
15         i++;                 
16     }    
17     return cnt; 
18 }


原创粉丝点击