Aho Corasick Algorithm

简介: 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的出现次数;

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)

目录
相关文章
|
5天前
|
算法 搜索推荐 大数据
算法(Algorithm)
算法(Algorithm)
60 0
|
5天前
|
机器学习/深度学习 算法 程序员
C++ Algorithm 库 算法秘境探索(Algorithm Wonderland Exploration)
C++ Algorithm 库 算法秘境探索(Algorithm Wonderland Exploration)
79 1
|
9月前
|
机器学习/深度学习 算法 TensorFlow
维特比算法(Viterbi algorithm)
维特比算法(Viterbi algorithm)是一种用于解码隐马尔可夫模型(Hidden Markov Model,HMM)的动态规划算法。它用于找到给定观测序列条件下的最有可能的隐藏状态序列。
256 1
|
7月前
|
算法
【Hello Algorithm】贪心算法
【Hello Algorithm】贪心算法
34 0
|
7月前
|
算法 数据挖掘 Serverless
k-means Clustering Algorithm
k-均值聚类算法(k-means Clustering Algorithm)是一种将一组数据分成 k 个不同的簇的聚类算法。该算法基于距离作为相似性度量,即将数据对象划分为 k 个簇,使得每个簇中的数据对象之间的距离尽可能小,而不同簇之间的数据对象之间的距离尽可能大。
39 2
|
10月前
|
算法 搜索推荐 程序员
Euclidean algorithm
数论算法是研究整数及其性质的算法。数论算法在密码学、编码、计算机科学和其他领域中有广泛的应用。以下是数论算法的一些常见的算法以及它们的实现方法和示例代码:
51 1
|
9月前
|
搜索推荐
algorithm常用函数
algorithm常用函数
|
C++ 容器
STL—algorithm(1)
在algorithm中,有很多函数,这些函数是已经写好的,可以直接调用,十分的方便,可以精简代码量辅助我们思考 在使用algorithm的函数之前需要添加头文件#include <algorithm>
74 0
|
C++ 容器
STL—algorithm(2)(上)
在algorithm中,有很多函数,这些函数是已经写好的,可以直接调用,十分的方便,可以精简代码量辅助我们思考 在使用algorithm的函数之前需要添加头文件#include <algorithm>
73 0
|
C++ 容器
STL—algorithm(2)(下)
容器的排序 并不是所有的STL容器都是可以用sort函数的,像vector,string是可以使用sort函数的,但是像set,map这种容器本身有序,故不允许使用sort排序
90 0