1. 程式人生 > >【模式匹配】Aho-Corasick自動機

【模式匹配】Aho-Corasick自動機

1. 多模匹配

AC自動機(Aho-Corasick Automaton)是多模匹配演算法的一種。所謂多模匹配,是指在字串匹配中,模式串有多個。前面所介紹的KMPBM為單模匹配,即模式串只有一個。假設主串\(T[1 \cdots m]\),模式串有k個\(\mathbb{P} = \{ P_1, \cdots, P_k\}\),且模式串集合的總長度為\(n\)。如果採用KMP來匹配多模式串,則演算法複雜度為:

\[ O(|P_1|+m+\cdots + |P_k|+m)=O(n+km) \]

而KMP並沒有利用到模式串之間的重複字元結構資訊,每一次的匹配都需要將主串從頭至尾掃描一遍。貝爾實驗室的Aho與Corasick於1975年基於有限狀態機(finite state machines)提出AC自動機演算法[1]。小插曲:實際上AC演算法比KMP提出要早,KMP是1977年才被提出來了的。

2. AC演算法

AC自動機

自動機由狀態(數字標記的圓圈)和轉換(帶標籤的箭頭)組成,每一次轉換對應一個字元。AC演算法的核心包括三個函式:goto、failure、output;這三個函式構成了AC自動機。對於模式串{he, his, hers, she},goto函式表示字元按模式串的轉移,暗含了模式串的共同字首的字元結構資訊,如下圖:

failure函式表示匹配失敗時退回的狀態:

output函式表示模式串對應於自動機的狀態:

完整的AC自動機如下:

匹配

AC演算法根據自動機匹配模式串,過程比較簡單:從主串的首字元、自動機的初始狀態0開始,

  • 若字元匹配成功,則按自動機的goto函式轉移到下一狀態;且若轉移的狀態對應有output函式,則輸出已匹配上的模式串;
  • 若字元匹配失敗,則遞迴地按自動機的failure函式進行轉移

匹配母串的演算法如下:

構造

AC自動機的確簡單高效,但是如何構造其對應的goto、failure、output函式呢?首先來看goto函式,細心一點我們發現goto函式本質上就是一棵帶有回退指標的trie樹,利用模式串的共同字首資訊,與output函式共同表示模式串的字元結構的資訊。

failure函式是整個AC演算法的精妙之處,用於匹配失敗時的回溯;且回溯到的狀態\(state\)應滿足:狀態\(state\)能按當前狀態的轉移字元進行能goto到的狀態,且能構成最長匹配。記\(g(r,a)=s\)表示狀態r可以按字元a goto到狀態s,則稱狀態r為狀態s的前一狀態,字元a為狀態s的轉移字元。failure函式滿足這樣一個規律:當匹配失敗時,回溯到的狀態為前一狀態的failure函式值(我們稱之為failure轉移狀態

)按轉移字元能goto到的狀態;若不能,則為前一狀態的failure轉移狀態的failure轉移狀態按轉移能goto到的狀態;若不能,則為......上面的話聽著有點拗口,讓我們以上圖AC自動機為例子來說明:

  • 對於狀態7,前一狀態6的failure轉移狀態為0,狀態0按轉移字元s可以goto到狀態3,所以狀態7的failure函式\(f(7)=3\)
  • 對於狀態2,前一狀態1的failure轉移狀態為0,狀態0按轉移字元e可以goto到狀態0,所以狀態2的failure函式\(f(2)=0\)

其中,所有root節點(狀態0)能goto到的狀態,其failure函式值均為0。根據goto表(trie樹)的特性,可知某一狀態的前一狀態、轉移字元是唯一確定的。因此定義\(\beta(s)=r\)表示狀態\(s\)的前一狀態為\(r\)\(\tau(s)=a\)指狀態\(s\)的轉移字元為\(a\);記\(f^{i}(s)=f\left( f^{(i-1)}(s)\right)\)。那麼,狀態s的failure函式的計算公式為:

\[ f(s) = \left\{ {\matrix{ {g\left( f^{n}(\beta(s)), \tau(s) \right)} & n = \arg \underset{i}{\min} \, \left\{ g\left( f^{i}(\beta(s)), \tau(s) \right) \neq failure \right\}\cr {0} & else \cr } } \right. \]

在計算failure函式時,巧妙地運用佇列進行遞迴構造,具體實現如下:

3. 實現

# coding=utf-8
from collections import deque, namedtuple

automaton = []
# state_id: int, value: char, goto: dict, failure: int, output: set
Node = namedtuple("Node", "state value goto failure output")


def init_trie(words):
    """
    creates an AC automaton, firstly create an empty trie, then add words to the trie
    and sets fail transitions
    """
    create_empty_trie()
    map(add_word, words)
    set_fail_transitions()


def create_empty_trie():
    """ initialize the root of the trie """
    automaton.append(Node(0, '', {}, 0, set()))


def add_word(word):
    """add word into trie"""
    node = automaton[0]
    for char in word:
        # char is not in trie
        if goto_state(node, char) is None:
            next_state = len(automaton)
            node.goto[char] = next_state  # modify goto(state, char)
            automaton.append(Node(next_state, char, {}, 0, set()))
            node = automaton[next_state]
        else:
            node = automaton[goto_state(node, char)]
    node.output.add(word)


def goto_state(node, char):
    """goto function"""
    if char in node.goto:
        return node.goto[char]
    else:
        return None


def set_fail_transitions():
    """construction of failure function, and update the output function"""
    queue = deque()
    # initialization
    for char in automaton[0].goto:
        s = automaton[0].goto[char]
        queue.append(s)
        automaton[s] = automaton[s]._replace(failure=0)
    while queue:
        r = queue.popleft()
        node = automaton[r]
        for a in node.goto:
            s = node.goto[a]
            queue.append(s)
            state = node.failure
            # failure transition recursively
            while goto_state(automaton[state], a) is None and state != 0:
                state = automaton[state].failure
            # except the chars in goto function, all chars transition will goto root node self
            if state == 0 and goto_state(automaton[state], a) is None:
                goto_a = 0
            else:
                goto_a = automaton[state].goto[a]
            automaton[s] = automaton[s]._replace(failure=goto_a)
            fs = automaton[s].failure
            automaton[s].output.update(automaton[fs].output)


def search_result(strings):
    """AC pattern matching machine"""
    result_set = set()
    node = automaton[0]
    for char in strings:
        while goto_state(node, char) is None and node.state != 0:
            node = automaton[node.failure]
        if node.state == 0 and goto_state(node, char) is None:
            node = automaton[0]
        else:
            node = automaton[goto_state(node, char)]
        if len(node.output) >= 1:
            result_set.update(node.output)
    return result_set


init_trie(['he', 'she', 'his', 'hers'])
print search_result("ushersm")

-------------------------------------------------------- 2016-06-14 更新 --------------------------------------------------------
實現了一個scala版本,支援新增詞屬性,程式碼託管在scala-AC

4. 參考資料

[1] Aho, Alfred V., and Margaret J. Corasick. "Efficient string matching: an aid to bibliographic search." Communications of the ACM 18.6 (1975): 333-340.
[2] Pekka Kilpeläinen, Lecture 4: Set Matching and Aho-Corasick Algorithm.

相關推薦

模式匹配Aho-Corasick自動機

1. 多模匹配 AC自動機(Aho-Corasick Automaton)是多模匹配演算法的一種。所謂多模匹配,是指在字串匹配中,模式串有多個。前面所介紹的KMP、BM為單模匹配,即模式串只有一個。假設主串\(T[1 \cdots m]\),模式串有k個\(\mathbb{P} = \{ P_1, \cdot

模式匹配KMP演算法的來龍去脈

1. 引言 字串匹配是極為常見的一種模式匹配。簡單地說,就是判斷主串\(T\)中是否出現該模式串\(P\),即\(P\)為\(T\)的子串。特別地,定義主串為\(T[0 \dots n-1]\),模式串為\(P[0 \dots p-1]\),則主串與模式串的長度各為\(n\)與\(p\)。 暴力匹配 暴力匹配

模式匹配更快的Boyer-Moore演算法

1. 引言 前一篇中介紹了字串KMP演算法,其利用失配時已匹配的字元資訊,以確定下一次匹配時模式串的起始位置。本文所要介紹的Boyer-Moore演算法是一種比KMP更快的字串匹配演算法,它到底是怎麼快的呢?且聽下面分解。 不同於KMP在匹配過程中從左至右與主串字元做比較,Boyer-Moore演算法是從模式

模式匹配之 —— KMP演算法詳解及證明

本文所述KMP演算法原始碼可在這裡下載: Name Date Reason for change Revision 超然 2013.03.19 First version 1.0 超然 2013.04.15 Added

圖片匹配--- SIFT_Opencv3.1.0_C++_ubuntu

文件 read 等待 s2d imread mage clas create detect   最近在搗鼓圖片相似性匹配算法。這裏先說一點必要的題外話: 如果是在同一個object不同角度拍攝的多張圖片中,使用SIFT可以有不錯的效果; 如果是尋找類別相同的圖片(可能不是同

算法後綴自動機SAM

同時 一個 自動 ... 包含 結合 不存在 相交 bsp 【Right集合】 後綴自動機真正優於後綴樹的方面在於:結合了有限狀態自動機,從而實現了O(n)的時空復雜度。 trans(s,str)表示s+str到達的狀態。 ST(str)=trans(init,str)

PHP PC端微信掃碼支付模式詳細教程-附帶源碼(轉)

idt class pid 方法 按鈕 -c 商戶 開源 玩意兒 博主寫這破玩意兒的時候花了大概快兩天時間才整體的弄懂邏輯,考慮了一下~還是把所有代碼都放出來給大家~抱著開源大無私的精神!誰叫我擅長拍黃片呢?同時也感謝我剛入行時候那些無私幫過我的程序員們! 首先還是

CF 612C. Replace To Make Regular Bracket Sequence括號匹配

pos cal set while ostream problem \n push || 【鏈接】:CF 【題意】:給你一個只含有括號的字符串,你可以將一種類型的左括號改成另外一種類型,右括號改成另外一種右括號 問你最少修改多少次,才能使得這個字符串匹配,輸出次數 【分析】

模式分解無損連線&保持函式依賴

首先引入定義     無損分解指的是對關係模式分解時,原關係模型下任一合法的關係值在分解之後應能通過自然聯接運算恢復起來。反之,則稱為有損分解。   保持函式依賴的分解指的是對關係分解時,原關係的閉包與分解後關係閉包的並集相等。    

POJ 3189 Steady Cow Assignment 二分+多重匹配

<題目連結> 題目大意: 有n頭牛,m個牛棚,每個牛棚都有一定的容量(就是最多能裝多少隻牛),然後每隻牛對每個牛棚的喜好度不同(就是所有牛圈在每個牛心中都有一個排名),然後要求所有的牛都進牛棚,牛棚在牛心中的排名差計算方法為:所有牛中最大排名和最小排名之差+1(包括區間端點)。問最小的排名差。

織夢熊掌號外掛,dedecms如何接入熊掌號API提交功能完美匹配

  百度熊掌號是內容和服務提供者入駐百度生態的認證賬號,致力於幫助內容和服務提供者便捷、高效地連線全網使用者,並充分利用百度生態開放的優勢,獲取流量、沉澱使用者、塑造品牌,實現自身價值的快速增長。 這裡我們只是使用了他的搜尋資源平臺的入口,提供我們網站資源。具體還要資料開放平臺

Bailian2976 Bailian1936 All in All字串匹配

2976:All in All 描述 給定兩個字串s和t,請判斷s是否是t的子序列。即從t中刪除一些字元,將剩餘的字元連線起來,即可獲得s。 輸入 包括若干個測試資料。每個測試資料由兩個ASCII碼的數字和字母串s和t組成,s和t的長度不超過100000。 輸出 對每個測試資料,如果s是t的子序列則輸出“Ye

資料結構——使用Java棧實現括號匹配

給定一個只包括 '(',')','{','}','[',']'的字串,判斷字串是否有效。 有效字串需滿足: 左括號必須用相同型別的右括號閉合。 左括號必須以正確的順序閉合。 注意空字串可被認為是有效字串。 參考leetcode.com或leetcode-cn.com

立體匹配Stereo Processing by Semiglobal Matching and Mutual Information(SGM)

Stereo Processing by Semiglobal Matching and Mutual Information 基於半全域性匹配和互資訊的立體處理 Stereo Processing by Semiglobal Matching and Mu

特徵匹配Harris及Shi-Tomasi原理及原始碼解析

演算法原理:呼叫cornerMinEigenVal()函式求出每個畫素點自適應矩陣M的較小特徵值,儲存在矩陣eig中,然後找到矩陣eig中最大的畫素值記為maxVal,然後閾值處理,小於qualityLevel*maxVal的特徵值排除掉,最後函式確保所有發現的角點之間具有足夠的距離。void cv::goo

字串匹配BKDRhash||KMP

題目描述 給定一個字串 A 和一個字串 B ,求 B 在 A 中的出現次數。A 和 B中的字元均為英語大寫字母或小寫字母。 A 中不同位置出現的 B 可重疊。 輸入格式 輸入共兩行,分別是字串 A 和字串 B 。 輸出格式 輸出一個整數,表示 B 在 A 中的出現次數。

模式識別SVM核函式

以下是幾種常用的核函式表示:線性核(Linear Kernel)多項式核(Polynomial Kernel)徑向基核函式(Radial Basis Function)也叫高斯核(Gaussian Kernel),因為可以看成如下核函式的領一個種形式:徑向基函式是指取值僅僅依

學習筆記迴文自動機

依舊是後面再補上講解吧希望我不要忘記惹。。。。 本質是一顆trie,節點代表了迴文的一半; 自動機節點維護長度和最大回文字尾的fail指標; 奇數迴文的根長度為-1,編號1,偶數0,1;(編號是有藝術的) 插入沿著上一個點找是否有匹配的迴文; 注意如果要新建節點的話應該再找新建點的fail,繼續想上

模式識別Fisher線性判別

Fisher是一種將高維空間對映到低維空間降維後進行分類的方法 1.投影: 對xn→的分量作線性組合可得標量 yn=w⃗ Txn→ 什麼樣的對映方法是好的,我們需要設計一個定量的標準去找w⃗ 來

模式識別Boosting

Boosting簡介分類中通常使用將多個弱分類器組合成強分類器進行分類的方法,統稱為整合分類方法(Ensemble Method)。比較簡單的如在Boosting之前出現Bagging的方法,首先從從整體樣本集合中抽樣採取不同的訓練集訓練弱分類器,然後使用多個弱分類器進行vo