首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >AC自动机

AC自动机

作者头像
hotarugali
发布2022-03-02 20:33:46
发布2022-03-02 20:33:46
1K00
代码可运行
举报
运行总次数:0
代码可运行

1. 简介

AC 自动机可以看作是字典树 + KMP,其主要构建步骤为:

  • 将所有模式串插入字典树中,构建出字典树
  • BFS 字典树上所有的结点构造失配指针(同时考虑路径压缩)

AC 自动机主要应用于多模式串匹配问题,即一个主串多个匹配的模式串问题。

2. 思想

AC 自动机基于字典树结构,将所有模式串插入字典树中,然后对字典树中的每个结点构造失配指针。AC 自动机中的失配指针与 KMP 中不同的是,AC 自动机中的失配指针是相对于整棵字典树的,即失配指针不再是局限于当前模式串,而是对于整棵字典树中所有的模式串而言的。

  • AC 自动机中的失配指针匹配的是当前模式串能匹配到的最长后缀对应的字典树中的结点,即从根结点出发能够匹配到的当前字符串最长后缀的结点。
  • 假设字典树中当前结点为 u,u 的父结点为 p,p 通过边字符 c指向 u,即 trie[p][c] = u ,结点失配数组为 fail。若深度小于 u 的所有结点的fail 指针都已经求得,则:
  1. 如果结点 trie[fail[p]][c] 存在,则

\begin{array}{c} fail[u] = trie[fail[p]][c] \end{array}

  1. 如果结点trie[fail[p]][c] 不存在,则递归计算p = fail[p],并判断结点 trie[fail[p]][c]是否存在 \cdots,直到找到或者指向根结点

\begin{array}{c} fail[u] = trie[fail[p]][c] \end{array}

即当前结点的失配指针指向存在的最近的祖先结点的失配指针对应结点连接边字符 c 指向的结点。

  • 由于求失配指针 fail数组时,要求深度小于当前结点的失配指针都已经计算出来了,所以在计算整棵字典树的 fail 时需要使用 BFS 遍历整棵字典树。
  • 构建好 fail指针数组后,就可以对主串进行匹配。
  • 考虑到寻找当前结点的失配指针指向存在的最近的祖先结点的失配指针对应结点连接边字符 c 指向的结点,这个过程可能由于trie[fail[p]][c]不存在而导致每次匹配结点失效时都要递归查找,从而浪费了比较多的时间。实际上由于构建 fail 指针数组时是对整棵字典树进行 BFS,因此可以对每个结点 u 的每条出边字符 c计算失配指针,即对于不存在的 trie[u][c]直接指向其存在的最近的祖先结点的失配指针对应结点连接边字符 c 指向的结点,从而使得下一次失配时如果判断到trie[u][c]不存在,可以直接得到存在的最近的祖先结点的失配指针对应结点连接边字符 c指向的结点。整体思想就是类似并查集的路径压缩。

3. 模板

代码语言:javascript
代码运行次数:0
运行
复制
#include <bits/stdc++.h>
using namespace std;

#ifndef _AUTOMATON_
#define _AUTOMATON_
#define ll int
#define MAXN 2000005
#define MAXCHAR 128

// AC 自动机
struct Automaton {
    ll cnt;                 // 动态开点
    ll trie[MAXN][MAXCHAR]; // 字典树
    ll exist[MAXN];         // 字符串标记
    ll fail[MAXN];          // 失配数组
    Automaton(): cnt(0) {}
    // 插入字符串(构建字典树)
    void insert(char *str, ll n) {
        ll p = 0;
        for(ll i = 0; i < n; ++i) {
            char ch = str[i];
            if(!trie[p][ch]) {
                trie[p][ch] = ++cnt;
            }
            p = trie[p][ch];
        }
        ++exist[p];
    }
    // 构建 AC 自动机
    void build() {
        queue <ll> q;
        // BFS 遍历字典树
        for(ll i = 0; i < MAXCHAR; ++i) {
            if(trie[0][i])  q.push(trie[0][i]);
        }
        while(q.size()) {
            ll u = q.front();
            q.pop();
            for(ll i = 0; i < MAXCHAR; ++i) {
                if(trie[u][i]) {
                    fail[trie[u][i]] = trie[fail[u]][i];
                    q.push(trie[u][i]);
                } else {
                    // 路径压缩
                    trie[u][i] = trie[fail[u]][i];
                }
            }
        }
    }
    // 多模式匹配
    // 统计主串中出现的不同模式串数量
    ll query(char *str, ll n) {
        ll u = 0, res = 0;
        for(ll i = 0; i < n; ++i) {
            u = trie[u][str[i]];
            // -1 标记优化(统计过一次后就不需再次统计)
            for(ll j = u; j && ~exist[j]; j = fail[j]) {
                res += exist[j];
                exist[j] = -1;
            }
        }
        return res;
    }
};
#endif
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020-09-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 简介
  • 2. 思想
  • 3. 模板
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档