多模式字符串匹配 - AC自动机原理及实现

AC自动机是一种多模匹配算法,源自1975年的贝尔实验室。它结合了字典树(Trie树)和失配指针,可以在字符串中高效查找多个子串。构造AC自动机包括构建字典树、设置失配指针和进行AC匹配过程。通过字典树存储词汇,失配指针对应最长后缀,匹配过程中通过失配指针快速定位,实现O(m+n)的时间复杂度。
摘要由CSDN通过智能技术生成

给一个字符串,找出字符串中指定的单词,如果是单个词,我们可以通过暴力解法,从头开始遍历字符串,判断是否存在指定的单词,当然还有一种更优的解法,那就是KMP算法,通过构造next数组,保存匹配的中间状态,在查找单词失败时,不用退回到字符串开头重新进行匹配,实现了O(m+n)的时间复杂度。这种在一个字符串中找一个单词的匹配成为单模式匹配,如果要在一个字符串中寻找多个子串了,那就是将要谈到的多模式匹配,如何实现呢?对每个子串进行KMP匹配,当然也可以。但是有一种更为优秀的多模式匹配算法,那就是AC自动机算法。

Aho-Corasick automaton,该算法在1975年产生于贝尔实验室,是著名的多模匹配算法。

AC自动机算法可以理解为树形的KMP算法,不过不了解KMP也不影响学习AC自动机算法,不过需要了解字典树和多叉树的广度遍历算法。

字典树

什么是Trie树?

Trie树,也叫字典树,是一种多叉树形数据结构,每个节点保存一个字符,一条路径上的所有节点保存一个字符串。以{"hers", "he", "his", "she"}构造的字典树为例:
在这里插入图片描述

如上图所示,每个单词在一条路径上,绿色节点表示该节点是一个单词的尾字符。其中root节点不存储任何字符。

如何构造一个字典树?

看上面的图应该已经知道如何构造一个Trie树了,那代码如何实现呢?首先定义节点的结构体:

struct TrieNode {
   
    TrieNode*                           fail{
   nullptr}; // 失配指针
    std::vector<int>                    exists;        // 该节点出是否存在完整单词
    std::unordered_map<char, TrieNode*> childs;        // 子节点,用 hash 表方便快速查询
};

其中:

exists字段用来存储以该节点字符结尾单词的长度,如单词"hers",则该节点exists = {4},用这个长度我们便可以截取出提取出字符串对应的该单词,而不用存储整个单词。

fail指针表示失配指针,后续将匹配过程会理解它。

childs用来存储子节点,为了快速定位子节点,我们用hash表存储子节点,键值为字符,value值为子节点其它信息

通过给定的多个单词构造Trie树代码如下:


TrieNode* buildTrie(const std::vector<std::string>& words) {
   
    if (words.empty()) {
   
        cout << "[buildTrie] words is empty" << endl;
        return nullptr;
    }
    TrieNode* root = new TrieNode();
    for (auto& word : words) {
   
        TrieNode* tmp = root;
        for (char ch : word) {
   
            if (tmp->childs.find(ch) == tmp->childs.end()) {
   
                tmp->childs[ch] = new TrieNode();
            }
            tmp = tmp->childs[ch];
        }
        tmp->exists.emplace_back(word.size());
    }
    return root;
}

不要忘记在每个单词的尾字符节点存储单词的长度

失配指针

失配指针的含义

先看已构造好失配指针的图,后续讲如何构造失配指针

在这里插入图片描述

如图所示,每个节点的失配指针都指向指定节点,有的指向root节点,有的指向字符节点,那失配指针代表什么呢?可以理解为:
如果节点A的失配指针指向节点B,那么就表示:节点B结尾的字符串是节点A结尾的字符串的最长后缀:

  • 节点[her]s.fail = ss是该字典树中hers的最长后缀
  • 节点[sh]e.fail = [h]ehe是该字典树中she的最长后缀
  • 节点[h]e.fail = roothe在该字典树中没有最长后缀

可以自己体会一下😂

构造失配指针

代码如下:


TrieNode* initAhoCorasick(const std::vector<std
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值