ac自动机求解多模式匹配问题

ac自动机求解多模式匹配问题
ac自动机,是有穷自动机的一种,主要用来解决多模式串匹配的问题。例如给你若干个关键字he she say her shr,然后给你一篇文章(长串) yasherhs,要求该串中总共出现了以上关键字多少次。
ac自动机主要分三步:
1、根据关键字构建trie树
2、根据trie树构造失败指针
3、在构造完失败指针的trie树上运行长串,得到结果
构建trie树可以达到O(a(m1 + m2 + ... + mi))复杂度,即为所有关键字长度的和。构建失败指针的复杂度同样平均为O(a(

  m 1 + m 2 + ... + m i )),其中a是构成字符串的字符集中字符个数。空间复杂度同样为O( a( m 1 + m 2 + ... + m i ))。在trie树上运行长串的时间为O(n)n为长串的长度。
构建trie树的方法比较简单,就和构造多叉树类似,这里不详述了,关键就是标识出哪些字符是一个关键字的结尾,即从root节点到当前节点恰好表示一个关键字。
关键问题是构造失败指针,其实这里构造失败指针的方法和kmp基本一样,ac自动机说白了就是构造树状KMP。
上篇文章有介绍KMP求失败指针的方法,就是针对子串运行KMP算法。
ac自动机 的失败指针构造有些类似,在trie树上运行一遍BFS,即可求得失败指针。
具体方法是,对当前节点p,若p的父亲节点的失败指针q的某个孩子节点表示的字符和p表示的字符相同,则p的失败指针指向q的对应的孩子,否则继续寻找q的失败指针,直到root。
构造完失败指针即可在ac自动机上匹配长串了,匹配方法如下,若当前长串a[i]与ac自动机当前节点p不匹配,则p = p->fail,继续匹配,若不匹配则继续向失败指针走,如果走到root,则从头匹配;否则若a[i]与当前节点p匹配,则查看节点p处有没有对应的关键词,若有则说明成功找到一个关键词。
下面以hdoj2222题为例看ac自动机代码,2222题是标准的多模式匹配题模版:

  1 #include <cstdio>
  2 #include <cstdlib>
  3 #include < string.h>
  4 #include <queue>
  5 
  6  #define MAX 26
  7 
  8 typedef  struct node {
  9    struct node *next;
 10    struct node *children[MAX];
 11    int words_amount;
 12 } NODE;
 13 
 14  static  void init_node(NODE *p) {
 15   p->next = NULL;
 16    int i = 0; 
 17    for (i = 0; i < MAX; ++i) {
 18     p->children[i] = NULL;
 19   }
 20   p->words_amount = 0;
 21 }
 22 
 23  void insert_to_trie( char *buf, NODE *root) {
 24    int len = strlen(buf);
 25    int i;
 26   NODE *p = root;
 27    for (i = 0; i < len; ++i) {
 28      int ch = buf[i] - 'a';
 29      if (p->children[ch] == NULL) {
 30       p->children[ch] = (NODE *)malloc( sizeof(NODE));
 31       init_node(p->children[ch]);
 32     }
 33     p = p->children[ch];
 34   }
 35   p->words_amount++;
 36 }
 37 
 38  void destroy_trie(NODE *root) {
 39    int i;
 40    for (i = 0; i < MAX; ++i) {
 41      if (root->children[i]) {
 42       destroy_trie(root->children[i]);
 43     }
 44   }
 45   free(root);
 46 }
 47 
 48  void bfs_for_next(NODE *root) {
 49    int i;
 50    if (!root) {
 51      return;
 52   }
 53   root->next = NULL;
 54   NODE *p = root;
 55   std::queue<NODE *> q;
 56   q.push(p);
 57    while (!q.empty()) {
 58     NODE *tmp = q.front();
 59     q.pop();
 60      for(i = 0; i < MAX; ++i) {
 61        if (tmp->children[i]) {
 62          if (tmp == root) {
 63           tmp->children[i]->next = root;
 64         }  else {
 65           NODE * pre = tmp->next;
 66            while (pre != NULL) {
 67              if (pre->children[i]) {
 68               tmp->children[i]->next = pre->children[i];
 69                break;
 70             }
 71             pre = pre->next;
 72           }
 73            if (pre == NULL) {
 74             tmp->children[i]->next = root;
 75           }
 76         }
 77         q.push(tmp->children[i]);
 78       }
 79     }
 80   }
 81 }
 82 
 83  int search( char *buf, NODE *root) {
 84    int i;
 85    int count = 0;
 86    int str_len = strlen(buf);
 87   NODE *cur_node = root;
 88    int cur_char;
 89    for (i = 0; i < str_len; ++i) {
 90     cur_char = buf[i] - 'a';
 91      while (cur_node != root && !cur_node->children[cur_char]) {
 92       cur_node = cur_node->next;
 93     }
 94     cur_node = cur_node->children[cur_char];
 95      if (!cur_node) {
 96       cur_node = root;
 97     }
 98     NODE *tmp = cur_node;
 99      while (tmp != root && tmp->words_amount > 0) {
100       count += tmp->words_amount;
101       tmp->words_amount = 0;
102       tmp = tmp->next;
103     }
104   }
105    return count;
106 }
107 
108  int main() {
109    int cases;
110    int n;
111    int i;
112    char *buf = ( char *)malloc( sizeof( char) * 1000005);
113   scanf("%d", &cases);
114    while (cases--) {
115     scanf("%d", &n);
116     NODE *root = (NODE *)malloc( sizeof(NODE));
117     init_node(root);
118      for (i = 0; i < n; ++i) {
119       scanf("%s", buf);
120       insert_to_trie(buf, root);
121     }
122     scanf("%s", buf);
123     bfs_for_next(root);
124     printf("%d\n", search(buf, root));
125     destroy_trie(root);
126   }
127 
128    return 0;
129 }

这里构造ac自动机的时候,每个节点都用malloc在堆中分配,当然也可以写成数组形式,效率应该会高一些,但是可扩展性就不够了

你可能感兴趣的:(ac自动机求解多模式匹配问题)