AC算法在文本匹配中的应用

在工作中需要对一些违禁词进行拦截,实际应用场景中,需保证匹配过程在数百毫秒内给出结果。因此,如何快速,准确的识别文本中是否包含了这些关键词就变得尤为重要。

对于上述问题我们描述为以下形式

给定关键词集合P={p1,p2,……,pk},在目标串T[1…m]中找到出现了哪些关键词。

最容易想到的方法就是针对每个单词去匹配一遍,最后总结出都哪些单词匹配成功。

考虑KMP算法,单个关键词匹配的时间复杂度是O(|pk|+m),所以,所有关键词都匹配一遍的时间复杂度为O(|p1|+m+|p2|+m+…+|pk|+m)。令n=|p1|+…+|pk|,上式化简为O(n+km),因此,当关键词的数量变得非常多时,这种算法就变得无法忍受了。

Alfred V.Aho和Margaret J.Corasick在1974年提出了一个经典的多模式匹配算法-AC算法,这个算法可以保证对于给定的长度为n的文本,和模式集合P{p1,p2,…pm},在O(n)的时间复杂度内找到文本中的所有目标模式,而与模式集合的规模m无关。

1. AC算法详解

AC自动机的基础是Trie树, 和Trie树不同的是,树中的每个结点除了有指向孩子的指针,根据被查找的目标字段开始往叶子节点逐字符匹配。在这个过程中,如果发生失配,要根据失配跳转点进行跳转,如果找到匹配的模式串则进行打印输出。AC算法在扫描文本时完全不需要回溯,如果只考虑匹配的过程,该算法的时间复杂度为O(n),也就是只跟待匹配文本的长度相关。

1.1 自动机

首先,我们解释下什么是AC自动机? 什么是自动机?

图1. 自动机状态转换图

自动机是有限状态机的数学模型,状态是什么? 比如:开关有两种状态,筛子有6种状态,如图中所示有A、B、C三种状态。自动机就是在某些外界刺激下,能够改变状态的机器。图中A状态,通过0,3,6,9的刺激,能够回滚到本身状态,通过1,4,7刺激可以到达B状态。

AC将字符匹配转换为了状态转移。即AC通过构建自动机,将字串匹配的过程,转换为了在状态机之间状态的跳转来表示字符匹配过程。

1.2 具体实现

AC算法的实现可由以下三个部分构成:

  • goto表:由模式集合中的所有模式构成的状态转移自动机
  • failure表:在goto表中匹配失败后状态跳转的依据
  • output表:表示输出,又称:emits,即代表到达某个状态后某个模式串匹配成功
(1)构造goto表

这里我们以集合P={“he”,”she”,”his”,”hers”} 为例

首先是构建转向函数(goto),指的是一种状态之间的转向关系,为了构建goto函数,我们需要建立一个状态转移图。开始,这个图只包含一个状态0,然后通过添加一条从起始状态出发的路径的方式,依次向图中输入每个关键字keyword,新的顶点和边被加入到图表中,这样就产生了一条能拼写出关键字keyword的路径。

添加第一个关键词“he”得到下图,其中从状态0到状态2的路径就拼写出了关键字“he”。

图2. he状态转移图

接着我们构建第二个关键词 "she"。

图3. she状态转移图

当我们增加“his”时,因为已经存在一条从状态0在输入h的条件下到达状态1的边,因此我们这里不需要另外添加一条同样的边。

图4. his状态转移图

最后我们添加 "hers",得到如下的链路,输出“hers”和状态9相关联,最后对除了h和s外的每个字符都增加一个从状态0到0的循环。

图5. hers状态转移图

至此,我们就构造了整个模式集合的状态转移图,也就是goto函数。

(2)构造failure表

失效函数failure决定了当goto函数得到的下一个状态无效时,应该回退到哪一个状态。

在构造fail函数时,我们首先要引入深度的概念,状态s的深度depth(s)定义为在goto表中从起始状态0到状态s的最短路径长度。如goto表中状态1和3的深度为1。

计算思路

先计算所有深度是1的状态的失效函数值,然后计算所有深度为2的状态,以此类推,直到所有状态(除了状态0,因为它的失效函数没有定义)的失效函数值都被计算出。

计算方法

用于计算某个状态失效函数值的算法在概念上是非常简单的。首先,令所有深度为1的状态s的函数值为f(s) = 0。假设所有深度小于d的状态的f值都已经被算出了,那么深度为d的状态的失效函数值将根据深度小于d的状态的失效函数值来计算。

计算过程

  • 对于所有的字符a,如果goto(r,a) = fail,那么什么也不做(当r为我们上面构造的trie树的叶子节点时,就符合这种情况)
  • 如果goto(r,a) == s,我们记state = fail®,执行state = f(state)零次或者若干次,直到使得goto(state,a) != fail,因为goto(0,a) != fail,所以这个状态是一定存在的
  • 记fail(s) = goto(state,a)

以我们上面构造的状态图为例, 我们构造出的failure表如下:

状态 0 1 2 3 4 5 6 7 8 9
fail值 None 0 0 0 1 2 0 3 0 3
(3)构造output表

output表示输出,即代表到达某个状态后某个模式串匹配成功。该表的构造过程融合在goto表和failure表的构造过程中。

  • 在构造goto表时,每个模式串结束的状态都加入到output表中,也就是goto表中的黑色加粗圆圈。得到:

    i output(i)
    2 {he}
    5 {she}
    7 {his}
    9 {hers}
  • 在构造failure表时,若f(s) = s’,则将s和s‘对应的output集合求并集。如f(5) = 2,则得到最终的output表为:

    i output(i)
    2 {he}
    5 {she, he}
    7 {his}
    9 {hers}

2. AC算法具体实现

以python为例

class ac_automation(object):

    def __init__(self):
        self.root = node()

    def add(self, word):
        temp_root = self.root
        for char in word:
            if char not in temp_root.next:
                temp_root.next[char] = node()
            temp_root = temp_root.next[char]
        temp_root.isWord = True
        temp_root.word = word

    def make_fail(self):
        temp_que = []
        temp_que.append(self.root)
        while len(temp_que) != 0:
            temp = temp_que.pop(0)
            p = None
            for key,value in temp.next.item():
                if temp == self.root:
                    temp.next[key].fail = self.root
                else:
                    p = temp.fail
                    while p is not None:
                        if key in p.next:
                            temp.next[key].fail = p.fail
                            break
                        p = p.fail
                    if p is None:
                        temp.next[key].fail = self.root
                temp_que.append(temp.next[key])

    def search(self, content):
        p = self.root
        result = []
        currentposition = 0

        while currentposition < len(content):
            word = content[currentposition]
            while word in p.next == False and p != self.root:
                p = p.fail

            if word in p.next:
                p = p.next[word]
            else:
                p = self.root

            if p.isWord:
                result.append(p.word)

            currentposition += 1
        return result
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容