zoukankan      html  css  js  c++  java
  • AC自动机

    本来这篇...我是不想写了的...以及比计划晚了三天...虽然是因为考试的原因....不过主要还是由于AC自动机这个算法我也不过是上周日的时候才学会怎么写。原理性东西有点了解而已。

    所以既然还是决定写了,那就写吧。

    AC自动机算法(Aho-Corasick算法)是由Alfred V. Aho和Margaret J.Corasick 发明的字符串搜索算法,在均摊情况下,具有近似于线性的时间复杂度。

    这个算法主要利用的就是一个Trie树,再加上一个失配指针,在"Trie树上跑KMP算法"。这个算法通常用于在一段文章中查找字典中的字符串出现的次数。


    首先,构造Trie树这个想必都是会的。然后就是构造失配指针。

    我所理解的失配指针,就是在当前匹配到失败的时候,跳转到另外一个与自身相同且具有相同前缀的字符上,如果没有相同的字符,就转到root上。不过这里所说的前缀,与一个字符串的前缀不同,但也类似吧。

    假设有一个如下图的字典:


    可以看出来,其字典里的词尾say,she,shr,her,hr,构造的Trie如上图。

    那么对于she中的he失配时,就会跳到her上的e字符,在这个过程中,her前缀为he,she的前缀也是he,不是通常意义上的前缀。(嘛...这只是我这一个星期的理解....可能不准确...以后理解好了回来在进一步阐述吧...

    其实这里的失配指针与KMP算法的失配指针非常的类似,所以才会经常说AC自动机算法就是在Trie树上跑KMP算法。


    所以最后构造的失配指针如下图:


    然后就是进行匹配了。

    对于给定字符串yasherhs

    首先对y匹配,而root的孩子中并没有y,于是对a匹配,同样也没有这个孩子,于是匹配到了s,发现s是root的孩子,于是字典树走到s;匹配h,刚好s的孩子中有一个为h,于是继续走到h;匹配e,刚好h有一个孩子是e,这时候经过判断得知,这已经是字典中she单词的最后一个单词,于是匹配到了这个单词。继续匹配,当这个单词匹配到了一个单词之后,还是转到了失配指针指向的地方,因为即使匹配了,对于下一个字符来说,也是失配了。所以走到第一层的h的孩子e上,与r匹配,失配,走到root上(图上蓝色线忘记画这条线了不用在意细节....),然后匹配h,发现root有这个孩子........最终匹配到了一个she


    下面给出C++的AC自动机代码:

    #include <bits/stdc++.h>
    using namespace std;
    
    const int maxn = 26;
    const int maxm = 1e6 + 5;
    
    typedef struct node{
        node *fail;         //失配指针
        node *child[maxn];    //儿子节点
        int point;          //标识这是第几个模式串的终止节点
        node() {
            fail = NULL;
            for( int i = 0; i < maxn; ++i )
                child[i] = NULL;
            point = -1;
        }
    }Trie;
    
    Trie *root;
    char str[maxm];
    
    void Insert( char *s, int num ) {
        Trie *p = root;
        for( char *c = s; *c != ''; ++c ) {
            int t = (*c) - 'a';
            if( p -> child[t] == NULL ) {
                p -> child[t] = new Trie;
            }
            p = p -> child[t];
            if( (*(c + 1)) == '' ) p -> point = num;
        }
    }
    
    void buildFailPointer() {
        queue<Trie* > q;
        while( !q.empty() ) q.pop();
    
        q.push(root);
        while( !q.empty() ) {
            Trie *now = q.front(); q.pop();
            for( int i = 0; i < maxn; ++i ) {
                if( now -> child[i] != NULL) {
                    if ( now == root ) now -> child[i] -> fail = root;
                    else {
                        Trie *p = now -> fail;
                        while( p != NULL ) {
                            if( p -> child[i] != NULL ) {
                                now -> child[i] -> fail = p -> child[i];
                                break;
                            }
                            p = p -> fail;
                        }
                        if ( p == NULL ) now -> child[i] -> fail = root;
                    }
                    q.push( now -> child[i] );
                }
            }
        }
    }
    
    int AC_auto() {
        int ret = 0;
        Trie *p = root;
        int len = strlen(str);
    
        for( int i = 0; i < len; ++i ) {
            int inx = str[i] - 'a';
            while( p -> child[inx] == NULL && p != root ) p = p -> fail;
            if( p -> child[inx] == NULL ) continue;
            p = p -> child[inx];
            Trie *t = p;
            while( t != root ) {
                if( t -> point != -1 ) ++ret;
                t = t -> fail;
            }
        }
        return ret;
    }
    int main() {
        root = new Trie;
        Insert( "she", 1 );
        Insert( "he", 2 );
        Insert( "say", 3 );
        Insert( "shr", 4 );
        Insert( "her", 5 );
    
        buildFailPointer();
        str[0] = 'y'; str[1] = 'a'; str[2] = 's'; str[3] = 'h';
        str[4] = 'e'; str[5] = 'r'; str[6] = 'h'; str[7] = 's';
        cout << AC_auto() << endl;
        return 0;
    }

    然而在写hihocoder第四周的时候,发现仅仅是AC自动机的话依旧是会超时的。所以发现其实有一个更加优化的算法,叫做Trie图,其实也就是AC自动机的优化版。不过这个算法。。。萌新只会喊666然后复制dalao代码,所以以后再更新这里吧。


    给出hihocoder-1036-Trie图的Java代码:

    import java.util.Scanner;
    import java.util.ArrayList;
    import java.io.BufferedInputStream;
    
    public class Main{
    	public static void main( String[] args ) {
    		@SuppressWarnings("resource")
    		Scanner in = new Scanner( new BufferedInputStream( System.in ) );
    		int n = Integer.parseInt( in.nextLine() );
    		
    		TrieGraph tg = new TrieGraph();
    		while(n-- > 0) {
    			String str = in.nextLine();
    			tg.add(str);
    		}
    		tg.build();
    		String str = in.nextLine();
    		if( tg.ac_auto( str ) ) {
    			System.out.println("YES");
    		} else {
    			System.out.println( "NO" );
    		}
    	}
    }
    class Node{
    	public char val = '';
    	public Node par = null;
    	public Node fail = null;
    	public boolean isEnd = false;
    	private static int maxn = 26;
    	public Node[] ch = new Node[maxn];
    	
    	Node(  ) {
    		this.val = '';
    		this.par = this;
    	}
    	Node( char c, Node p ) {
    		this.val = c;
    		this.par = p;
    	}
    	public Node getNext( char c ) {
    		Node next = this.ch[ c - 'a' ];
    		if( next == null ) next = this.ch[ c - 'a' ] = new Node( c, this );
    		return next;
    	}
    	public void setRootFail( Node root ) {
    		this.fail = root;
    	}
    	public void setFail(  ) {
    		this.fail = par.fail.ch[ this.val - 'a' ];
    	}
    	public void setNext( int index ) {
    		this.ch[ index ] = fail.ch[ index ];
    	}
    }
    class TrieGraph{
    	private Node root = new Node( '$', null );
    	
    	public void add( String str ) {
    		int len = str.length();
    		Node p = root;
    		for( int i = 0; i < len; ++i ) {
    			p = p.getNext( str.charAt(i) );
    		}
    		p.isEnd = true;
    	}
    	public void build() {
    		ArrayList<Node> q = new ArrayList<Node>();
    		root.setRootFail(root);
    		for ( int i = 0; i < 26; ++i ) {
    			if( root.ch[i] == null ) {
    				root.ch[i] = root;
    			} else {
    				q.add( root.ch[i] );
    			}
    		}
    		while( q.size() > 0 ) {
    			Node p = q.get(0);
    			if( p.par == root ) {
    				p.setRootFail(root);
    			} else {
    				p.setFail();
    			}
    			
    			for ( int i = 0; i < 26; ++i ) {
    				if( p.ch[i] == null ) {
    					p.setNext( i );
    				} else {
    					q.add( p.ch[i] );
    				}
    			}
    			q.remove(0);
    		}
    	}
    	public boolean ac_auto( String str ) {
    		Node p = root;
    		int len = str.length();
    		
    		for( int i = 0; i < len; ++i ) {
    			p = p.getNext( str.charAt(i) );
    			if( p.isEnd ) return true;
    		}
    		return false;
    	}
    }


  • 相关阅读:
    Android JNI和NDK学习(04)--NDK调试方法(转)
    Android JNI和NDK学习(03)--动态方式实现JNI(转)
    Android JNI和NDK学习(02)--静态方式实现JNI(转)
    Android JNI和NDK学习(01)--搭建NDK开发环境(转)
    C++语言基础(7)-inline内联函数
    C++语言基础(6)-const 关键字
    C++语言基础(5)-this和static关键字
    红黑树:个人理解与Python实现
    最小堆实现优先队列:Python实现
    二叉查找树:Python实现
  • 原文地址:https://www.cnblogs.com/wiklvrain/p/8179336.html
Copyright © 2011-2022 走看看