zoukankan      html  css  js  c++  java
  • 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);
        }
    }
    
  • 相关阅读:
    [杂题]CSUOJ1274Balls and Boxes
    [Gauss]POJ1222 EXTENDED LIGHTS OUT
    [杂题]CSUOJ1413 Area of a Fractal
    [AC自动机]HDOJ3695 Computer Virus on Planet Pandora
    [dp]POJ2559 && HDOJ1506 Largest Rectangle in a Histogram
    [TSP+floyd]POJ3311 Hie with the Pie
    [状压dp]HDOJ3182 Hamburger Magi
    [状压dp]HDOJ1565 方格取数(1)
    [dp]Codeforces30C Shooting Gallery
    树套树模版
  • 原文地址:https://www.cnblogs.com/hyserendipity/p/8572881.html
Copyright © 2011-2022 走看看