AC 自動機の基礎と応用

AC 自動機は、複数のパターン文字列を効率的に検索するためのアルゴリズムです。この記事では、その基本的な構造と動作原理について説明します。

前提知識

AC 自動機を理解するためには、まずは字典木(Trie木)とKMPアルゴリズムの概念を理解しておく必要があります。

問題設定

KMPアルゴリズムは単一のパターン文字列に対する文字列マッチングを行いますが、複数のパターン文字列に対して効率的にマッチングを行うためにAC自動機を使用します。

AC 自動機の基本構造

AC 自動機は、複数のパターン文字列から構成されるTrie木に失敗リンク(失配ポインタ)を追加したものです。

実装手順

ステップ1: Trie木の構築

まず、各パターン文字列をTrie木に挿入します。各ノードには子ノードへのリンク、失敗リンク、およびパターンの終端を示すフラグが含まれます。

struct Node {
    int fail;
    int children[26];
    bool terminal;
};

Node trie[MAX_NODES];
int node_count = 1;

void insert(const string& pattern) {
    int current = 0;
    for (char c : pattern) {
        int index = c - 'a';
        if (trie[current].children[index] == 0) {
            trie[current].children[index] = node_count++;
        }
        current = trie[current].children[index];
    }
    trie[current].terminal = true;
}

ステップ2: 失敗リンクの計算

BFSを使用して各ノードの失敗リンクを計算します。失敗リンクは、現在のノードの文字に対応する子ノードが存在しない場合に移動すべきノードを示します。

void build_fail_links() {
    queue<int> q;
    for (int i = 0; i < 26; ++i) {
        if (trie[0].children[i] != 0) {
            q.push(trie[0].children[i]);
        } else {
            trie[0].children[i] = 0;
        }
    }
    while (!q.empty()) {
        int current = q.front();
        q.pop();
        for (int i = 0; i < 26; ++i) {
            int child = trie[current].children[i];
            if (child != 0) {
                int fail = trie[current].fail;
                while (trie[fail].children[i] == 0 && fail != 0) {
                    fail = trie[fail].fail;
                }
                trie[child].fail = trie[fail].children[i];
                if (trie[trie[child].fail].terminal) {
                    trie[child].terminal = true;
                }
                q.push(child);
            } else {
                trie[current].children[i] = trie[trie[current].fail].children[i];
            }
        }
    }
}

ステップ3: マッチングの実行

与えられたテキストに対してマッチングを行います。現在のノードから始めて、テキストの各文字に対応する子ノードへ遷移し、必要に応じて失敗リンクを使用して次のノードへ移動します。

vector<string> match_patterns(const string& text) {
    vector<string> matches;
    int current = 0;
    for (char c : text) {
        int index = c - 'a';
        while (trie[current].children[index] == 0 && current != 0) {
            current = trie[current].fail;
        }
        current = trie[current].children[index];
        int temp = current;
        while (temp != 0) {
            if (trie[temp].terminal) {
                matches.push_back(text.substr(current - trie[temp].depth, trie[temp].depth));
            }
            temp = trie[temp].fail;
        }
    }
    return matches;
}

タグ: Trie木 KMP AC自動機 文字列マッチング

6月28日 23:12 投稿