2018/1/29训练日记 AC自动机

今天就看了AC自动机

定义和概念什么的都不说了 直接放模板   今天搞明白了原理,然后操作过程在模板的每一个地方我都加上了注释

半天的劳动成果啊,,,,,,至于原理什么的可以去参考下大佬们的博客,原理写的都是蛮清楚的

为了搞清楚AC自动机是个啥 又看视频又看博客又看Word文档,最后加上讨论,还是有几个地方不是很清楚

不过讨论是个好东西,一讨论好多地方就清楚了一点

我这个模板是用指针写的,而书上和饶齐的模板都是用的数组,但是感觉指针的代码更容易读懂一些,如果大家想看的话

可以在理解完原理的基础上来找我这个模板去看,可能更容易理解一些,有可能看不懂的地方我都加上了注释,都是心血

啊,心态炸裂。。。。。。


#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;
//:给你一个文本和多个单词,问你出现了多少个单词。注意单词可能会重复。
const int MAXN = 1000001; //模式串的最大长度MAXN - 1
const int MAXM = 51; //单词最大长度为MAXM - 1
const int KEYSIZE = 26; //26个小写字母
struct Node {
      Node *fail;  //失配指针 指向和当前结点的某个点的后缀匹配的最长前缀的位置
      Node *next[KEYSIZE]; //儿子结点个数 子节点指针
      int count; //单词个数
      Node() {
            fail = NULL;
            count = 0;
            memset(next, 0, sizeof(next));
      }
     ~Node() {
          delete next;
      }
}*q[MAXN/2];
void insert(char *str, Node *root)//模式串插入   构建字典树
{
      Node *p = root;
      int i = 0;
      while(str[i]){
                //获取字母对应id
           int index = str[i] - 'a';
           //检查子节点是否存在,不存在则创建新的子结点
           if(p -> next[index] == NULL)
                  p -> next[index] = new Node();
           p = p -> next[index];
           i ++;
      }
      p -> count ++; //在单词的最后一个结点count + 1,代表一个单词
}
void build_ac_automation(Node *root)//构建fail指针 基于bfs实现
{
      root -> fail = NULL;
      //根结点的失配指针,直接指向NULL
      //对于根结点下所有的子节点(与根结点直接相连的结点的fail肯定直接指向root)
      //失配指针一定是指向root的(当一个字符都不能匹配的时候,自然不存在更短的能够与之匹配的前缀了)
      int head, tail;
      head = tail = 0;
      q[tail ++] = root;
      //逐层计算每一层的结点的fail指针
      //当每个结点计算完毕,保证它所有的后继都计算出来
      //每次弹出一个结点temp,询问它的每个字符对应的子结点
      //第i个子结点记为 temp->next[i]


      while(head < tail) {
            Node *temp = q[head ++];
            for(int i = 0; i < KEYSIZE; i ++) {
                        //如果temp->next[i]==NULL
                        //那么temp->next[i]指向temp的失配指针的i号子结点
                        //temp->next[i]=temp->fail->next[i]
                        //如果temp->next[i]不等于NULL
                        //需要构造temp->next[i]的失配指针
                        //temp[i]->next[i]->fail=temp[i]->fail->next[i]
                if(temp -> next[i] != NULL) {
                     if(temp == root){
                          temp -> next[i] -> fail = root;
                     }else {
                          Node *p = temp -> fail;
                          while(p != NULL) {
                               if(p -> next[i] != NULL) {
                                         temp -> next[i] -> fail = p -> next[i];
                                    break;
                               }
                               p = p -> fail;
                          }
                          if(p == NULL)
                               temp -> next[i] -> fail = root;
                     }
                     q[tail ++] = temp -> next[i];
                }
           }
      }
}
//利用fail指针进行匹配
int query(char *str, Node *root)//目标串匹配
{
      int i = 0, cnt = 0;
      Node *p = root;
      while(str[i]) {
           int index = str[i] - 'a';
           //根据目标串给定的字符进行遍历
           //每次检查当前结点是否为结尾结点
           //还需要检查失配指针指向的结点
           while(p -> next[index] == NULL && p != root) p = p -> fail;
           p = p -> next[index];
           //当前单词从来没有出现过的话直接回到匹配之初
           p = (p == NULL) ? root : p;
           Node *temp = p;
           //找到一个子串以temp结点结尾
           //可以统计子串数目或者是输出子串的位置
           //累加所有的count即为模式串的个数
           while(temp != root && temp -> count != -1) {
                 cnt += temp -> count;
                 temp -> count = -1;
                 temp = temp -> fail;
           }
           i ++;
      }
      return cnt;
}
int main()
{
      int n, cas;
      Node *root;
      char keyword[MAXM]; //单词
      char str[MAXN]; //模式串
      scanf("%d", &cas);//case
      while(cas --) {
            scanf("%d", &n);
            getchar();
            root = new Node();
            while(n --) {
                 scanf("%s", keyword);
                 insert(keyword, root);
            }
            build_ac_automation(root);
            scanf("%s", str);
            printf("%d\n", query(str, root));
      }
      return(0);
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值