AC自动机详解

如果想了解更多内容,欢迎关注我的微信公众号:信息学竞赛从入门到巅峰。

今天我们来介绍一点进阶的知识——AC自动机。

AC自动机是什么呢?是不是用了这个算法,不管什么题目都会自动AC呢?(别做梦啦~)

AC自动机,是Aho-Corasick automaton的简称,该算法在1975年产生于贝尔实验室,是著名的多模匹配算法。AC自动机是对字典树算法的一种延伸,是字符串中运用非常广泛的一种算法,但是NOIP一般不会涉及,多在省选及以上的比赛中出现。

我们先来回顾一下字典树的实现:

struct node {
int id;
    node* nxt[26];
    node() {
        id = -1;
for (int i = 0; i < 26; ++i)
            nxt[i] = NULL;
    }
};

class TrieTree {
public:
  TrieTree() { root = new node; }
  int get_id(node* x) { return x->id; }
  node* get_root() { return root; }
  node* get_nxt(node* x, char k) { return x->nxt[k - 'a']; }
  void insert(char* c, int id) {
      int len = strlen(c);
      node* now = root;
      for (int i = 0; i < len; ++i) {
          int x = c[i] - 'a';
          if ((now->nxt[x]) == NULL)
              now->nxt[x] = new node;
          now = now->nxt[x];
      }
      now->id = id;
    }
private:
    node* root;
}t;

AC自动机比字典树多维护一个数组——fail数组。fail数组的作用是指向当前节点表示的字符串的后缀可以和模式串匹配上的最大长度的节点。

是不是和KMP的next数组有点相似?

KMP的next数组是自己和自己的匹配,而AC自动机的fail数组是自己和模式串(当然也包括自己)的匹配。看一下下面这张图,应该会对fail数组有深刻的理解。

这张图中的红色线条就是对应节点fail数组所指向的节点,都指向了能和改字符串后缀匹配的最长前缀。

下面我们来讲讲具体的实现吧。

观察上面的那张图,一个节点的fail指针(暂且这么叫)指向的节点,和它的父节点(若u节点通过一步转移能到达v节点,则称u为v的父节点)fail指针指向的位置是有关系的。

既然只和父节点的fail指针有关,那么我们采用队列的数据结构和处理每个节点的fail指针。设当前节点为x。

代码如下(仔细琢磨一下bfs的过程):

  • 初始化时,应建立一个虚拟节点,将这个节点的所有出边连向根节点,把根节点的fail指针指向虚拟节点。(注意:根节点代表空串)

  • 遍历x的每一种边(注意:是“种”,不是“条”,即包括没有的边)。(这部分仔细捋一捋)

    • 如果x没有这条边,如果x的fail节点有连这种边,那么x的这条边连向fail节点的这种边连向的点。

    • 如果x有这种边,那么其连向的节点的fail指向x的fail的这种边连向的点。

代码如下(仔细琢磨一下bfs的过程):


struct node {
    node* nxt[26];
    node* fail;
    int id;
    node() {
        fail = NULL; id = -1;
        for (int i = 0; i < 26; ++i)
            nxt[i] = NULL;
    }
};
class AC_Machine {
public:
    void init() {
        root = new node[1];
        emp = new node[1];
    }
    int get_id(node *x) { return x->id; }
    node *get_root() { return root; }
    node *get_nxt(node *x, int k) { return x->nxt[k]; }
    node *get_fail(node *x) { return x->fail; }
    void insert(char *c, int id) {
        int len = strlen(c);
        node *now = root;
        for (int i = 0; i < len; ++i) {
            int x = c[i] - 'a';
            if ((now->nxt[x]) == NULL)
                now->nxt[x] = new node[1];
            now = now->nxt[x];
        }
        now->id = id;
    }
    void build() {
        node *now;
        queue <node *> q;
        root->fail = emp;
        for (int i = 0; i < 26; ++i)
            emp->nxt[i] = root;
        q.push(root);
        while (!q.empty()) {
            now = q.front(); q.pop();
            for (int i = 0; i < 26; ++i) {
                if ((now->nxt[i]) == NULL) {
                    now->nxt[i] = now->fail->nxt[i];
                } else {
                    now->nxt[i]->fail = now->fail->nxt[i];
                    q.push(now->nxt[i]);
                }
            }
        }
    }
private:
    node *root, *emp;
    void clean(node *ro) {
        for (int i = 0; i < 26; ++i)
            if (ro->nxt[i] != NULL)
                clean(ro->nxt[i]);
        delete ro;
    }
}t;

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值