Aho-Corasick算法


2018-03-15 10:25:02

在計算機科學中,Aho–Corasick算法是由Alfred V. Aho和Margaret J.Corasick 發明的字符串搜索算法,用於在輸入的一串字符串中匹配有限組“字典”中的子串。它與普通字符串匹配的不同點在於同時與所有字典串進行匹配。算法均攤情況下具有近似於線性的時間復雜度,約為字符串的長度加所有匹配的數量。

AC自動機主要依靠構造一個有限狀態機(類似於在一個trie樹中添加失配指針)來實現。這些額外的失配指針允許在查找字符串失敗時進行回退(例如設Trie樹的單詞cat匹配失敗,但是在Trie樹中存在另一個單詞cart,失配指針就會指向前綴ca),轉向某前綴的其他分支,免於重復匹配前綴,提高算法效率。

當一個字典串集合是已知的(例如一個計算機病毒庫), 就可以以離線方式先將自動機求出並儲存以供日后使用,在這種情況下,算法的時間復雜度為輸入字符串長度和匹配數量之和。

UNIX系統中的一個命令fgrep就是以AC自動機算法作為基礎實現的。

 

一、自動機

自動機是計算理論的一個概念,其實是一張“圖”,每個點是一個“狀態”,而邊則是狀態之間的轉移,根據條件能指導從一個狀態走向另一個狀態。很多字符串匹配算法都是基於自動機模型的,比如被廣泛使用的正則表達式。

 

二、AC自動機

AC自動機可以看成是對KMP算法的推廣,KMP算法是一種單模字符串匹配算法,AC自動機是多模字符串匹配算法,可以一次對多個pattern進行匹配。

AC自動機的建立流程也很簡單,主要分為以下幾步:

1.建Trie樹

2.在Trie樹上建立失配指針,成為AC自動機

3.自動機上匹配字符串

下面以模式串he/ she/ his /hers為例,待檢測文本為“ushers”。

1、建立Trie樹

建立Trie樹可以說是非常模板了。

    class TrieNode {
        TrieNode[] children;
        TrieNode fail;
        boolean isWord;

        TrieNode() {
            children = new TrieNode[26];
            fail = null;
            isWord = false;
        }
    }

    TrieNode root;

    void bulidTrie(String[] patterns) {
        root = new TrieNode();
        for (String pattern : patterns) {
            TrieNode cur = root;
            for (int i = 0; i < pattern.length(); i++) {
                if (cur.children[pattern.charAt(i) - 'a'] == null)
                    cur.children[pattern.charAt(i) - 'a'] = new TrieNode();
                cur = cur.children[pattern.charAt(i) - 'a'];
            }
            cur.isWord = true;
        }
    }

2、建立失配指針

AC自動機的核心就是建立失配指針,其思路和KMP算法非常類似,在KMP中如果文本的text[i...j] 和 pattern[0...j - i]在text[j]出失配,KMP采取的思路是計算pattern[0...j - i - 1]的最長公共前后綴,然后將pattern向后滑動數位,從最長公共前后綴之后繼續比較,如果依然失配,則重復上述的流程,直到到首位,如果依然失配,則text下移。

在AC自動機中也是這樣,構造失敗指針的過程概括起來就一句話:設這個節點上的字母為C,沿着他父親的失敗指針走,直到走到一個節點,他的兒子中也有字母為C的節點。然后把當前節點的失敗指針指向那個字母也為C的兒子。如果一直走到了root都沒找到,那就把失敗指針指向root。具體操作起來只需要:先把root加入隊列(root的失敗指針指向自己或者NULL),這以后我們每處理一個點,就把它的所有兒子加入隊列,隊列為空。

    void core() {
        Queue<TrieNode> queue = new LinkedList<TrieNode>();
        queue.add(root);
        while (!queue.isEmpty()) {
            TrieNode cur = queue.poll();
            for (int i = 0; i < 26; i++) {
                if (cur.children[i] != null) {
                    if (cur == root) cur.children[i].fail = root;
                    else {
                        TrieNode tmp = cur.fail;
                        while (tmp != null) {
                            if (tmp.children[i] != null) {
                                cur.children[i].fail = tmp.children[i];
                                break;
                            }
                            tmp = tmp.fail;
                        }
                        if (tmp == null) cur.children[i].fail = root;
                    }
                    queue.add(cur.children[i]);
                }
            }
        }
    }

3、在自動機上進行匹配

    int query(String text) {
        int res = 0;
        TrieNode pre = root;
        for (int i = 0; i < text.length(); i++) {
            int index = text.charAt(i) - 'a';
            while (pre.children[index] == null && pre != root) pre = pre.fail;
            if (pre == root && pre.children[index] == null) continue;
            pre = pre.children[index];
            TrieNode tmp = pre;
            while (tmp != root && tmp.isWord) {
                res++;
                tmp.isWord = false;
                tmp = tmp.fail;
            }
        }
        return res;
    }

完整代碼:

import java.util.LinkedList;
import java.util.Queue;

public class AhoCorasick {
    class TrieNode {
        TrieNode[] children;
        TrieNode fail;
        boolean isWord;

        TrieNode() {
            children = new TrieNode[26];
            fail = null;
            isWord = false;
        }
    }

    TrieNode root;

    AhoCorasick() {
        root = new TrieNode();
    }

    void bulidTrie(String[] patterns) {
        for (String pattern : patterns) {
            TrieNode cur = root;
            for (int i = 0; i < pattern.length(); i++) {
                if (cur.children[pattern.charAt(i) - 'a'] == null)
                    cur.children[pattern.charAt(i) - 'a'] = new TrieNode();
                cur = cur.children[pattern.charAt(i) - 'a'];
            }
            cur.isWord = true;
        }
    }

    void core() {
        Queue<TrieNode> queue = new LinkedList<TrieNode>();
        queue.add(root);
        while (!queue.isEmpty()) {
            TrieNode cur = queue.poll();
            for (int i = 0; i < 26; i++) {
                if (cur.children[i] != null) {
                    if (cur == root) cur.children[i].fail = root;
                    else {
                        TrieNode tmp = cur.fail;
                        while (tmp != null) {
                            if (tmp.children[i] != null) {
                                cur.children[i].fail = tmp.children[i];
                                break;
                            }
                            tmp = tmp.fail;
                        }
                        if (tmp == null) cur.children[i].fail = root;
                    }
                    queue.add(cur.children[i]);
                }
            }
        }
    }

    int query(String text) {
        int res = 0;
        TrieNode pre = root;
        for (int i = 0; i < text.length(); i++) {
            int index = text.charAt(i) - 'a';
            while (pre.children[index] == null && pre != root) pre = pre.fail;
            if (pre == root && pre.children[index] == null) continue;
            pre = pre.children[index];
            TrieNode tmp = pre;
            while (tmp != root && tmp.isWord) {
                res++;
                tmp.isWord = false;
                tmp = tmp.fail;
            }
        }
        return res;
    }

    public static void main(String[] args) {
        AhoCorasick ac = new AhoCorasick();
        String[] patterns = new String[]{"he", "she", "his", "hers"};
        ac.bulidTrie(patterns);
        ac.core();
        int ans = ac.query("ushers");
        System.out.println(ans);
    }
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM