美文网首页
多模式串匹配 - AC 自动机

多模式串匹配 - AC 自动机

作者: 微微笑的蜗牛 | 来源:发表于2019-11-10 14:05 被阅读0次

多模式串匹配概念

多模式串匹配,即多个模式串在一个主串中进行匹配。

虽然单模式串也能完成多模式串的匹配,但每个模式串都需要与主串进行匹配,如果遇到主串太长的情况,效率不高。

而多模式串匹配,只需要扫描一次主串,大大提高效率。

有一种经典的多模式串匹配算法 -- AC 自动机,全称是 Aho-Corasick 算法。下面来介绍其实现。

AC 自动机

AC 自动机Trie 树的基础上,增加了类似 KMPnext 数组,即每个节点会有个 fail 指针,指向某个节点。数据结构如下:

class ACNode {
    var data: Character
    
    // 字符集 26 个小写字母
    var children: [ACNode?]
    var isEnd: Bool = false
    var fail: ACNode? = nil
    var len: Int = 0
    
    init(_ data: Character) {
        self.data = data
        children = [ACNode]()
        
        // 初始化 26 个
        var i = 0
        while i < 26 {
            children.append(nil)
            i += 1
        }
    }
}

准备工作

  1. 将多个模式串构建成 Trie
  2. 构建 fail 指针

构建 Trie 树

参考 Trie 树 的文章。

构建 fail 指针

fail 指针的生成类似于 KMP 中的next 数组计算方式,基于前一个值来计算。

fail 指针的定义

每个节点都有 fail 指针。假设节点为 p,当前串 strrootp 的节点路径,找到其与所有模式串前缀匹配的最长后缀子串,那么 fail 就指向所匹配前缀的最后一个字符节点。

后缀子串:不包括开头字符的子串。

这里需要注意的是最长后缀子串,比如当前串为 abc,模式串为 bcc。虽然其与 bcc 都匹配,但最长的后缀子串是 bc

其中root.fail = NULL,若proot的直接子节点,则p.fail = root

fail 指针的生成方法

假设当前节点为 pp.fail = qpc 记为 p 的某个子节点,qc 记为 q 的某个子节点。

  1. pc == qc,则 pc.fail = qc。如下图所示:
image.png
  1. pc != qc,则不断循环 q = q.fail,直到找到 q 的子节点与 pc 相等,或者 q == root结束。但如果 q == root,则说明没有后缀与模式串的前缀匹配,此时则令 pc.fail = root。再继续这两个过程。
image.png

按照树的广度遍历,逐个生成节点的 fail 指针。

最终结果如下图所示:

image.png

Swift 代码如下:

// 构建失败指针
func buildFailPointer(_ root: ACNode) {
    var queue = Array<ACNode>()
    queue.append(root)
    
    while !queue.isEmpty {
        // 取出头部
        let p = queue.removeFirst()
        
        // 遍历其子节点
        if !p.children.isEmpty {
            for pc in p.children {
                if let pc = pc {
                    // 如果父节点是 root,则 fail 指针直接指向 root
                    if p === root {
                        pc.fail = root
                    } else {
                        var q = p.fail
                        while q != nil {
                            // 如果 pc 在 q 的子节点 qc 中存在,则直接指向 qc
                            let index = indexOfChar(pc.data)
                            if (index >= 0 && index <= q!.children.count) {
                                if let qc = q!.children[index] {
                                    pc.fail = qc
                                } else {
                                    // 不存在,找 q 的 fail 指针
                                    q = q!.fail
                                }
                            }
                        }
                        
                        // 不存在,则指向 root
                        if q == nil {
                            pc.fail = root
                        }
                    }
                    
                    queue.append(pc)
                }
            }
        }
    }
}

模式串匹配

首先需要明确以下两点:

  1. p 的节点路径 A ( root 到 p 的路径)匹配主串,则 p.fail 的节点路径 B 也是匹配的。因为 A 的后缀子串与B 的前缀是相同的,所以前面肯定匹配,同理 p.fail.fail 的节点路径也是。

    因此只需要不断遍历其 fail 指针节点,判断是否为结束符,如果是,则该模式串就是匹配主串的。

  2. 在某条分支路径上 A 做匹配,如果遇上不匹配的情况,则切到其 fail 指针指向的另外一条分支 B ,再继续匹配。因为其前后缀相同。

具体算法

逐个遍历主串的字符,判断该字符是否存在当前节点 p 的子节点中。

  1. 如果存在,则 p 指向其子节点,然后循环遍历 p 链式的 fail 指针指向的节点是否为模式串的结尾,若是,该模式串匹配完成。
  2. 如果不存在,则循环遍历 fail 的链式指针进行查找,若没找到,则节点 p 重新指回 root。重复这 2 个步骤。

Swift 代码如下:

// 进行匹配
func match(_ text: String, _ root: ACNode) {
    // 逐个遍历主串
    var p: ACNode? = root
    var i = 0
    while i < text.count {
        let strIndex = text.index(text.startIndex, offsetBy: i)
        
        let ch = text[strIndex]
        
        // 判断 p 的子节点是否匹配 ch,如果不匹配,则往 fail 指针找
        let index = indexOfChar(ch)
        
        while p?.children[index] == nil && p !== root {
            p = p?.fail
        }
        
        p = p?.children[index]
        
        // 一直没有匹配,重新指回 root
        if p == nil {
            p = root
        }
        
        // 遍历其 fail 指针,找到结束的字符,即为匹配
        var tmp = p
        while tmp != nil {
            if tmp!.isEnd {
                print("match startPosition:\(i - tmp!.len + 1)")
            }
            
            tmp = tmp?.fail
        }
        
        i += 1
    }
}

相关文章

  • AC 自动机

    AC自动机 AC自动机是一个经典的多模式串匹配算法,它可以实现对主串的一次扫描来匹配多个模式串的功能。实现AC自动...

  • AC自动机实现屏蔽单词

    多模式自动匹配AC自动机 KMP是多模式匹配算法, 解决的是一个字符串匹配多个模式串的问题, 该字符串往往短于或者...

  • AC自动机

    参考资料:AC自动机GIF动图(来自油管) 以下文章节选自:王争老师 AC自动机:如何用多模式串匹配实现敏感词过滤...

  • 多模式串匹配 - AC 自动机

    多模式串匹配概念 多模式串匹配,即多个模式串在一个主串中进行匹配。 虽然单模式串也能完成多模式串的匹配,但每个模式...

  • AC 自动机——多模式串匹配

    网站上的敏感词过滤是怎么实现的呢? 实际上,这些功能最基本的原理就是字符串匹配算法,也就是通过维护一个敏感词的字典...

  • AC自动机 - 过滤敏感词

    今天我们学习一种多模式匹配算法:AC自动机。(这个名字可能不太严谨,自动机知识这个算法的一个部分)多模式匹配算法:...

  • AC自动机

    AC自动机(Aho-Corasick\ automaton),可以解决多模板串匹配的问题。可以理解为可以一次性匹配...

  • AC自动机 图文介绍

    预备知识 Trie(字典树)KMP字符串匹配算法 AC自动机求解问题的类型 一句话概括就是:多模匹配。KMP求解的...

  • AC自动机及多模式匹配

    在接触AC自动机之前,只仅仅掌握单模式匹配的算法:比如KMP、BMH等算法;经过优化后,KMP和BMH都具有线性时...

  • AC自动机_模板

    AC自动机: 求多个字符串是否在主串中出现过。可依据情况分别求出出现次数,出现位置等。 AC自动机入门Keywor...

网友评论

      本文标题:多模式串匹配 - AC 自动机

      本文链接:https://www.haomeiwen.com/subject/atcabctx.html