Skip to the content.

:heavy_check_mark: Aho-Corasick
(string/aho-corasick.hpp)

複数のパターン文字列を Aho-Corasick automaton で検索する.

パターン文字列から構築した Trie に failure link と遷移を追加した automaton である.検索文字列を先頭から 1 文字ずつ automaton に通すことで,すべてのパターンを同時に検索する.

AhoCorasick<char_size, char_start> として使う.扱える文字は char_start 以上 char_start + char_size 未満とし,空のパターン文字列は扱わない.すべてのパターンを追加してから build() を 1 回呼び,以降はパターンを追加しないことを要求する.

アルゴリズム

Trie の各ノードを,root からそのノードまでの文字列と同一視する.ノード $v$ の failure link を,$v$ 自身を除く suffix のうち Trie に存在する最長のものに対応するノードへの辺とする.

failure link は root に近いノードから BFS 順に構築する.ノード $v$ から文字 $c$ に対応する Trie の辺が存在する場合,遷移先を $u$ とすると,$u$ の failure link は $v$ の failure link から $c$ で遷移した先となる.辺が存在しない場合は,$v$ の failure link から $c$ で遷移した先を $v$ からの遷移先とする.root に対応する遷移先が存在しない場合は root へ戻る.

各ノードには,そのノードで終端するパターンに加えて,failure link の遷移先で終端するパターンも保持する.これにより,検索時には現在のノードが保持する ID を列挙すれば,その位置で終わるすべてのパターンを取得できる.

パターンの長さの総和を $L$,failure link に沿って複製されるパターン ID 数を $K$ とすると,build() は $O(char_size\cdot L+K)$ 時間,$O(char_size\cdot L+K)$ 空間.検索文字列の長さを $N$,報告する出現数を $M$ とすると,match() は期待 $O(N+M)$ 時間,match_any() は $O(N)$ 時間.

資料

Verified with

Code

#pragma once

template <int char_size = 26, char char_start = 'a'>
struct AhoCorasick {
  struct Node {
    int prev, c, fail;
    int next[char_size];
    vector<int> accept;
    Node(int prev, int c) : prev(prev), c(c), fail(-1) {
      memset(next, -1, sizeof(next));
    }
  };

  AhoCorasick() : root(make_node()), str_count(0) {}

  int add(const string& str) {
    assert(!str.empty());
    int id = str_count++;
    int x = root;
    for (auto c : str) x = get_child(x, char_index(c));
    nodes[x].accept.emplace_back(id);
    return id;
  }
  int size() const { return str_count; }
  int node_size() const { return (int)nodes.size(); }
  int get_root() const { return root; }
  const vector<int>& accept(int x) const { return nodes[x].accept; }

  void build() {
    queue<int> qu;
    for (int i = 0; i < char_size; i++) {
      int& x = nodes[root].next[i];
      if (x == -1) {
        x = root;
      } else {
        qu.push(x);
        nodes[x].fail = root;
      }
    }
    while (!qu.empty()) {
      int x = qu.front();
      qu.pop();
      int f = nodes[x].fail;
      auto& accept_x = nodes[x].accept;
      auto& accept_f = nodes[f].accept;
      vector<int> accept;
      set_union(accept_x.begin(), accept_x.end(), accept_f.begin(), accept_f.end(), back_inserter(accept));
      swap(accept_x, accept);
      for (int i = 0; i < char_size; i++) {
        int& y = nodes[x].next[i];
        if (y == -1) {
          y = nodes[f].next[i];
        } else {
          qu.push(y);
          nodes[y].fail = nodes[f].next[i];
        }
      }
    }
  }

  int move(int x, const char c) const {
    int y = nodes[x].next[c - char_start];
    if (y == -1) y = nodes[x].fail;
    return y;
  }
  unordered_map<int, int> match(const string& s) const {
    int x = root;
    unordered_map<int, int> count;
    for (auto c : s) {
      x = move(x, c);
      for (auto id : nodes[x].accept) count[id]++;
    }
    return count;
  }
  int match_any(const string& s) const {
    int x = root;
    int count = 0;
    for (auto c : s) {
      x = move(x, c);
      count += nodes[x].accept.size();
    }
    return count;
  }

 private:
  vector<Node> nodes;
  int root, str_count;
  int make_node(int prev = -1, int c = -1) {
    nodes.push_back(Node(prev, c));
    return nodes.size() - 1;
  }
  int get_child(int index, int char_index) {
    if (nodes[index].next[char_index] == -1) {
      int child = make_node(index, char_index);
      nodes[index].next[char_index] = child;
    }
    return nodes[index].next[char_index];
  }
  int char_index(char c) const {
    int x = c - char_start;
    assert(0 <= x && x < char_size);
    return x;
  }
};

/**
 * @brief Aho-Corasick
 * @docs docs/string/aho-corasick.md
 */
#line 2 "string/aho-corasick.hpp"

template <int char_size = 26, char char_start = 'a'>
struct AhoCorasick {
  struct Node {
    int prev, c, fail;
    int next[char_size];
    vector<int> accept;
    Node(int prev, int c) : prev(prev), c(c), fail(-1) {
      memset(next, -1, sizeof(next));
    }
  };

  AhoCorasick() : root(make_node()), str_count(0) {}

  int add(const string& str) {
    assert(!str.empty());
    int id = str_count++;
    int x = root;
    for (auto c : str) x = get_child(x, char_index(c));
    nodes[x].accept.emplace_back(id);
    return id;
  }
  int size() const { return str_count; }
  int node_size() const { return (int)nodes.size(); }
  int get_root() const { return root; }
  const vector<int>& accept(int x) const { return nodes[x].accept; }

  void build() {
    queue<int> qu;
    for (int i = 0; i < char_size; i++) {
      int& x = nodes[root].next[i];
      if (x == -1) {
        x = root;
      } else {
        qu.push(x);
        nodes[x].fail = root;
      }
    }
    while (!qu.empty()) {
      int x = qu.front();
      qu.pop();
      int f = nodes[x].fail;
      auto& accept_x = nodes[x].accept;
      auto& accept_f = nodes[f].accept;
      vector<int> accept;
      set_union(accept_x.begin(), accept_x.end(), accept_f.begin(), accept_f.end(), back_inserter(accept));
      swap(accept_x, accept);
      for (int i = 0; i < char_size; i++) {
        int& y = nodes[x].next[i];
        if (y == -1) {
          y = nodes[f].next[i];
        } else {
          qu.push(y);
          nodes[y].fail = nodes[f].next[i];
        }
      }
    }
  }

  int move(int x, const char c) const {
    int y = nodes[x].next[c - char_start];
    if (y == -1) y = nodes[x].fail;
    return y;
  }
  unordered_map<int, int> match(const string& s) const {
    int x = root;
    unordered_map<int, int> count;
    for (auto c : s) {
      x = move(x, c);
      for (auto id : nodes[x].accept) count[id]++;
    }
    return count;
  }
  int match_any(const string& s) const {
    int x = root;
    int count = 0;
    for (auto c : s) {
      x = move(x, c);
      count += nodes[x].accept.size();
    }
    return count;
  }

 private:
  vector<Node> nodes;
  int root, str_count;
  int make_node(int prev = -1, int c = -1) {
    nodes.push_back(Node(prev, c));
    return nodes.size() - 1;
  }
  int get_child(int index, int char_index) {
    if (nodes[index].next[char_index] == -1) {
      int child = make_node(index, char_index);
      nodes[index].next[char_index] = child;
    }
    return nodes[index].next[char_index];
  }
  int char_index(char c) const {
    int x = c - char_start;
    assert(0 <= x && x < char_size);
    return x;
  }
};

/**
 * @brief Aho-Corasick
 * @docs docs/string/aho-corasick.md
 */
Back to top page