返回

字典树到自动机——AC自动机的构造、应用与实现

闲谈

字典树
简介
字典树(Trie树)是一种基于字符串的树形数据结构。它可以用于快速检索和插入字符串,并支持前缀匹配和通配符匹配等多种操作。

特点

  • 节点上保存着某个字符。
  • 从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串。
  • 一个节点的所有子节点路径代表的字符串具有公共前缀。
  • 如果某个节点的所有子节点都是叶节点,那么该节点就是最后一个字符的父节点。

应用

  • 词频统计
  • 拼写检查
  • 字符串匹配
  • 数据压缩

自动机
简介
自动机(Finite Automaton,FA)是一种抽象的数学模型,用于一组状态、状态之间的转换和状态的输入和输出。自动机可以用来解决许多计算机科学问题,如词法分析、语法分析和模式匹配。

特点

  • 由有限个状态组成。
  • 每个状态都有一个或多个输入符号。
  • 当自动机处于某个状态时,如果收到一个输入符号,它将根据该输入符号转移到另一个状态。
  • 某些状态被指定为接受状态,当自动机处于接受状态时,它将接受输入的字符串。

应用

  • 词法分析
  • 语法分析
  • 模式匹配
  • 自然语言处理
  • 机器学习

字典树到自动机
转换过程
将字典树转换为自动机(AC自动机)的过程如下:

  1. 将字典树的根节点作为自动机的初始状态。
  2. 对于字典树中的每个节点,创建一个新的状态。
  3. 对于字典树中的每个节点,将该节点的每个子节点与一个输入符号相关联,并将该输入符号对应的状态设置为该子节点的状态。
  4. 将字典树中的所有叶节点标记为接受状态。

应用

  • 字符串匹配
  • 文本搜索
  • 数据挖掘
  • 自然语言处理

实现
C++实现

class TrieNode {
public:
    TrieNode* children[26];
    bool isEndOfWord;
    TrieNode() {
        for (int i = 0; i < 26; i++) {
            children[i] = nullptr;
        }
        isEndOfWord = false;
    }
};

class Trie {
public:
    TrieNode* root;
    Trie() {
        root = new TrieNode();
    }

    void insert(string word) {
        TrieNode* curr = root;
        for (char c : word) {
            int index = c - 'a';
            if (curr->children[index] == nullptr) {
                curr->children[index] = new TrieNode();
            }
            curr = curr->children[index];
        }
        curr->isEndOfWord = true;
    }

    bool search(string word) {
        TrieNode* curr = root;
        for (char c : word) {
            int index = c - 'a';
            if (curr->children[index] == nullptr) {
                return false;
            }
            curr = curr->children[index];
        }
        return curr->isEndOfWord;
    }
};

class ACAutomata {
public:
    Trie* trie;
    ACAutomata() {
        trie = new Trie();
    }

    void buildFailureLinks() {
        queue<TrieNode*> q;
        q.push(trie->root);

        while (!q.empty()) {
            TrieNode* curr = q.front();
            q.pop();

            for (int i = 0; i < 26; i++) {
                TrieNode* child = curr->children[i];
                if (child != nullptr) {
                    TrieNode* failure = curr;
                    while (failure != nullptr && failure->children[i] == nullptr) {
                        failure = failure->failure;
                    }
                    if (failure == nullptr) {
                        child->failure = trie->root;
                    } else {
                        child->failure = failure->children[i];
                    }

                    if (child->failure->isEndOfWord) {
                        child->isEndOfWord = true;
                    }

                    q.push(child);
                }
            }
        }
    }

    void search(string text) {
        TrieNode* curr = trie->root;

        for (char c : text) {
            int index = c - 'a';
            while (curr != nullptr && curr->children[index] == nullptr) {
                curr = curr->failure;
            }
            if (curr == nullptr) {
                curr = trie->root;
            } else {
                curr = curr->children[index];
            }

            if (curr->isEndOfWord) {
                // Do something when a pattern is found
            }
        }
    }
};

Python实现

class TrieNode:
    def __init__(self):
        self.children = {}
        self.isEndOfWord = False

class Trie:
    def __init__(self):
        self.root = TrieNode()

    def insert(self, word):
        curr = self.root
        for char in word:
            if char not in curr.children:
                curr.children[char] = TrieNode()
            curr = curr.children[char]
        curr.isEndOfWord = True

    def search(self, word):
        curr = self.root
        for char in word:
            if char not in curr.children:
                return False
            curr = curr.children[char]
        return curr.isEndOfWord

class ACAutomata:
    def __init__(self):
        self.trie = Trie()

    def buildFailureLinks(self):
        queue = [self.trie.root]

        while queue:
            curr = queue.pop(0)

            for char in curr.children:
                child = curr.children[char]
                failure = curr
                while failure and char not in failure.children:
                    failure = failure.failure
                if failure:
                    child.failure = failure.children[char]
                if child.failure.isEndOfWord:
                    child.isEndOfWord = True

                queue.append(child)

    def search(self, text):
        curr = self.trie.root

        for char in text:
            while curr and char not in curr.children:
                curr = curr.failure
            if curr:
                curr = curr.children[char]

            if curr.isEndOfWord:
                # Do something when a pattern is found

a = ACAutomata()
a.trie.insert("he")
a.trie.insert("she")
a.trie.insert("his")
a.trie.insert("hers")
a.buildFailureLinks()
a.search("ahis")