Aho Corasick Algorithm

文章目录

前言

Aho Corasick Algorithm又叫AC自动机,该算法是一个匹配算法,用来匹配文本Text中多个patterns分别出现的次数;

我们定义npatterns的总长度;mText的长度;

问题:在ahishershe文本中找出以下"he", "she", "hers", "his"各个patterns出现的次数;

最直接的暴力解法时间时间复杂度为O(n*m),如果采用KMP Algorithm,会把时间度降低为O(n+m),但是这是在单一的pattern的情况下,在k个pattern的情况下,应该成倍的增长m,时间复杂度为O(n+k*m),使用Aho Corasick Algorithm可以把时间优化为O(n+m+z),其中z表示出现的次数;

介绍

AC自动机利用的前缀树Trie这一数据结构;前缀树是一种很简单的结构,其构造如下:

我们可以构建Trie然后使用窗口进行暴力搜索,其时间复杂度为O(m*max(L)),这里面的max(L)Trie的深度;

这里面可以优化的一个点是,我们可以利用不匹配词的最长后缀作为词的前缀去优化查找,而不是不匹配就重新从root开始;找到最长的公共后缀将保证我们不需要再次检查那么多字符串,并且在每次不匹配之后我们都会重复上一步骤;

如图所示:

这些指向最长后缀的节点的链接被称为suffix linksfailure links,在匹配不成功的时候,若有最长的后缀节点,指向最长的后最节点,若没有则指向root进行重新查找;

假设在一颗Trie上有字符串w,x,...,其中xw的最长的后缀,所以有红线连接wa,若存在waxa,这xa也是wa的最长后缀,也用红线连接;

xa不存在,就去找除了x以外的和w的最大公共后缀,如果发现y符合条件,可以对y进行匹配;如果y不符合条件,就接着找除了x,y以外的和w的最大公共后缀,依次进行;

其次需要优化的一点输出环节,如果pattern1pattern2的后缀的情况下,我们可能会忽略掉pattern1,所以在寻找最大后缀时需要进行判断,如果最大后缀结点是pattern,就用output link连接原结点指向该结点,在以该结点为原结点去连接,直到root;如果不是就判断该结点的最大后缀结点是否是pattern,再用原结点去连接这一个结点;

在文本sting中,遍历到i的时候,可以发现sti的最大后缀ti并不是pattern,于是就找ti的最大后缀i,是pattern,于是用蓝色的线把stii结合起来;这里加粗了这一过程;

在遍历到n的时候,发现tinin都是pattern,依次进行连接

这时时间复杂度为O(m+z),其中z表示pattern的出现次数;

实现

下面是python实现的代码:

python 复制代码
from collections import defaultdict


class ahocorasick:
    def __init__(self, words):
        self.max_states = sum([len(word) for word in words])
        self.max_characters = 26
        self.out = [0] * (self.max_states + 1)
        self.fail = [-1] * (self.max_states + 1)
        self.goto = [[-1] * self.max_characters for _ in range(self.max_states + 1)]

        for i in range(len(words)):
            words[i] = words[i].lower()

        self.words = words
        self.states_count = self.__build_matching_machine()

    def __build_matching_machine(self):
        k = len(self.words)

        states = 1
        for i in range(k):
            word = self.words[i]
            current_state = 0
            for character in word:
                ch = ord(character) - 97
                if self.goto[current_state][ch] == -1:
                    self.goto[current_state][ch] = states
                    states += 1

                current_state = self.goto[current_state][ch]
            self.out[current_state] |= (1 << i)
        for ch in range(self.max_characters):
            if self.goto[0][ch] == -1:
                self.goto[0][ch] = 0
        queue = []
        for ch in range(self.max_characters):
            if self.goto[0][ch] != 0:
                self.fail[self.goto[0][ch]] = 0
                queue.append(self.goto[0][ch])
        while queue:
            state = queue.pop(0)
            for ch in range(self.max_characters):
                if self.goto[state][ch] != -1:
                    failure = self.fail[state]
                    while self.goto[failure][ch] == -1:
                        failure = self.fail[failure]

                    failure = self.goto[failure][ch]
                    self.fail[self.goto[state][ch]] = failure
                    self.out[self.goto[state][ch]] |= self.out[failure]
                    queue.append(self.goto[state][ch])

        return states

    def __find_next_state(self, current_state, next_input):
        answer = current_state
        ch = ord(next_input) - 97
        while self.goto[answer][ch] == -1:
            answer = self.fail[answer]

        return self.goto[answer][ch]

    def search_words(self, text):
        text = text.lower()
        current_state = 0

        result = defaultdict(list)

        for i in range(len(text)):
            current_state = self.__find_next_state(current_state, text[i])

            if self.out[current_state] == 0: continue

            for j in range(len(self.words)):
                if (self.out[current_state] & (1 << j)) > 0:
                    word = self.words[j]
                    result[word].append(i - len(word) + 1)

        return result


if __name__ == "__main__":
    words = ["he", "she", "hers", "his"]
    text = "ahishershe"

    aho_chorasick = ahocorasick(words)

    result = aho_chorasick.search_words(text)

    for word in result:
        for i in result[word]:
            print("Word", word, "appears from", i, "to", i + len(word) - 1)

参考

Aho Corasick Algorithm (opengenus.org)

相关推荐
迷藏49410 小时前
**发散创新:基于Python与深度学习的情绪识别实战全流程解析**在人工智能快速发展的今天,**情绪识别(Emoti
java·人工智能·python·深度学习
Ashore11_10 小时前
蓝桥杯16届Java研究生组
java·算法·蓝桥杯
6Hzlia10 小时前
【Hot 100 刷题计划】 LeetCode 76. 最小覆盖子串 | C++ 滑动窗口题解
c++·算法·leetcode
像素猎人10 小时前
蓝桥杯OJ2049蓝桥勇士【动态规划】【dp[n]不是符合题意的答案,只是以an结尾的子问题的答案】
c++·算法·蓝桥杯·动态规划·区间dp
羊小猪~~10 小时前
LLM--SFT简介
python·考研·算法·ai·大模型·llm·微调
无心水10 小时前
17、Java内存溢出(OOM)避坑指南:三个典型案例深度解析
java·开发语言·后端·python·架构·java.time·java时间处理
广州灵眸科技有限公司10 小时前
瑞芯微(EASY EAI)RV1126B 人脸98关键点算法识别
开发语言·科技·嵌入式硬件·物联网·算法·php
篮子里的玫瑰10 小时前
FreeRTOS:信号量与互斥量在DMA串口发送中的实战剖析
stm32·单片机·嵌入式硬件·算法
hughnz10 小时前
钻头技术持续突飞猛进:地热钻探领域的创新
人工智能·算法
xiaoye-duck10 小时前
《算法题讲解指南:动态规划算法--子数组系列》--21.乘积最大子数组,22.乘积为正数的最长子数组
c++·算法·动态规划