利用AC自动机进行关键字的提取和过滤

昨天看了meituan.com的AC算法在美团上单系统的应用一文,深受启发,原来ACM算法在工程中也能有这样赤裸裸的运用~~~ 于是便复习了AC自动机,并把代码用java重新搞了一遍~~

AC自动机整体的结果大概是长这样的,其实就是在trie树上做KMP :

AC自动机里面比较难理解的应该是它的失配指针的计算过程。

这个计算过程从本质上讲就是进行一遍广搜,于此同时维护

fail指针,每一步的维护过程可用下图表示。

Keyword.java

package com.AC.domain;

import java.io.*;
import java.util.*;
import java.math.*;

public class Keyword implements Serializable{

	/**
	 *
	 */

	private Integer id;
	private Map<Integer, Integer> categoryTypeMap;
	private String word;
	private List<Integer> categories;

	private static final long serialVersionUID = 1L;

	public Keyword(){
		id = null;
		categories=null;
		categoryTypeMap=null;
		word=null;
	}

	public Keyword(String key){
		id = null;
		categories=null;
		categoryTypeMap=null;
		word=key;
	}

	public Keyword(Keyword p){
		this.categories=p.categories;
		this.categoryTypeMap=p.categoryTypeMap;
		this.id=p.id;
		this.word=p.word;
	}

	@Override
	public boolean equals(Object o) {
		// TODO Auto-generated method stub

		if (this == o) return true;
		if(o==null||getClass()!=o.getClass()) return false;

		Keyword keyword = (Keyword) o;

		if(id!=null?!id.equals(keyword.id):keyword.id!=null)
			return false;

		return true;
	}
	@Override
	public int hashCode() {
		// TODO Auto-generated method stub
		return id != null ?id.hashCode():0;
	}
	public Integer getId() {
		return id;
	}
	public void setId(Integer id) {
		this.id = id;
	}
	public Map<Integer, Integer> getCategoryTypeMap() {
		return categoryTypeMap;
	}
	public void setCategoryTypeMap(Map<Integer, Integer> categoryTypeMap) {
		this.categoryTypeMap = categoryTypeMap;
	}
	public String getWord() {
		return word;
	}
	public void setWord(String word) {
		this.word = word;
	}
	public List<Integer> getCategories() {
		return categories;
	}
	public void setCategories(List<Integer> categories) {
		this.categories = categories;
	}

}

Node.java

package com.AC.domain;

import java.util.ArrayList;
import java.util.List;

public class Node {
	public Integer state;
	public char  character = 0;  //鎸囧悜褰撳墠鑺傜偣鐨勫瓧绗?
	public Node failureNode;
	public List <Keyword> keywords;
	public List <Node> childrenList;

	public Node(){
		keywords=new ArrayList<Keyword>();
		childrenList = new ArrayList<Node>();
		state = 0;
		failureNode = null;
		character = 0;
	}

	public Node (char c,Node node) {
		keywords=new ArrayList<Keyword>();
		childrenList = new ArrayList<Node>();
		state =1;
		character =c ;
		failureNode = node;
	}

	public Boolean containsChild (char c){
		for(Node childNode : childrenList) {
			if(childNode.character==c) return true;
		}
		return false;
	}

	public Node getChild (char c){
		for (Node childNode : childrenList){
			if(childNode.character==c) return childNode;
		}
		return null;
	}

	public void addKeyword(Keyword keyword){
		keywords.add(keyword);

	}

	public void addKeywords(List<Keyword> k){
		keywords.addAll(k);
	}

	public void addChild(Node child){
		childrenList.add(child);
	}

}

Patterns.java

package com.AC.domain;

import java.util.*;
import java.io.*;
import java.math.*;

public class Patterns {
	private final Node root = new Node();

	private List<Node> tree;

	public Patterns(List<Keyword> keywords){
		tree = new ArrayList<Node> ();
		root.failureNode=root;
		tree.add(root);
		for(Keyword keyword : keywords){
			addKeyword(keyword);
		}
		setFailNode();
	}

	private  void setFailNode() {
		// TODO Auto-generated method stub

		Queue<Node> queue = new LinkedList<Node>();
		Node node =root;
		for (Node d1 : node.childrenList){
			queue.offer(d1);
		}
		while (!queue.isEmpty()){
			node = queue.poll();
			if (node.childrenList!=null){
				for (Node curNode : node.childrenList) {
					queue.offer(curNode);
					Node failNode = node.failureNode;
					while(!failNode.containsChild(curNode.character)){
						failNode = failNode.failureNode;
						if(failNode==null||failNode.state==0) break;
					}
					if(failNode!=null&&failNode.containsChild(curNode.character)) {
						curNode.failureNode = failNode.getChild(curNode.character);
						curNode.addKeywords(curNode.failureNode.keywords);

					}

				}
			}
		}
	}

	private  void addKeyword(Keyword keyword) {
		// TODO Auto-generated method stub

		char [] wordCharArr = keyword.getWord().toCharArray();
		Node current = root;
		for(char currentChar : wordCharArr){
			if(current.containsChild(currentChar)){
				current = current.getChild(currentChar);
			}
			else{
				Node node = new Node (currentChar,root);
				current.addChild(node);
				current=node;
				tree.add(node);
			}
		}
		current.addKeyword(keyword);

	}

	public List<Keyword> searchKeyword(String data,Integer category) {
		List<Keyword> matchResult = new ArrayList<Keyword>();
		Node node = root;
		char[] chs = data.toCharArray();
		for (int i=0;i<chs.length;i++){
			while(node!=null&&!node.containsChild(chs[i])){
			//	if(node.state==0) break;
				node = node.failureNode;
				if(node==null||node.state==0) break;
			}

			if(node!=null&&node.containsChild(chs[i])) {
				node = node.getChild(chs[i]);
				if(node.keywords!=null){
					for(Keyword pattern : node.keywords){
						if(category == null){
	//						System.out.println(pattern.getWord());
							matchResult.add(new Keyword(pattern.getWord()));
						}
						else{
							if(pattern.getCategories().contains(category)){
								matchResult.add(pattern);
							}
						}

					}
				}
			}
		}
		return matchResult;
	}

}

Test.java

package com.AC.domain;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class Test {
	public static void main(String []args){

	//	abcd abc abe ae bc be bce cm kcabcmgh

		List<Keyword> keywords = new ArrayList<Keyword>();
		List<Keyword> result = new ArrayList<Keyword> ();

/*		List<Keyword> re= new ArrayList<Keyword> ();
		re.clear();
		Keyword a= new Keyword("abcd");
		re.add(a);
		Keyword b= new Keyword("abc");
		re.add(b);

		System.out.println(re.size());*/

		Keyword a1= new Keyword();
		a1.setWord("abcd");
		keywords.add(a1);

		Keyword a2= new Keyword();
		a2.setWord("abc");
		keywords.add(a2);

		Keyword a3= new Keyword();
		a3.setWord("abe");
		keywords.add(a3);

		Keyword a5= new Keyword();
		a5.setWord("ae");
		keywords.add(a5);	

		Keyword a6= new Keyword();
		a6.setWord("bc");
		keywords.add(a6);	

		Keyword a7= new Keyword();
		a7.setWord("be");
		keywords.add(a7);	

		Keyword a8= new Keyword();
		a8.setWord("bce");
		keywords.add(a8);	

		Keyword a9= new Keyword();
		a9.setWord("cm");
		keywords.add(a9);	

		Patterns patterns=new Patterns(keywords);
		result=patterns.searchKeyword("kcabcmgha", null);

//		System.out.println(result.size());
		System.out.println("keys: ");
		for(Keyword key:result){
			System.out.println(key.getWord());
		}

	//	System.out.println(result);
	}

}

附美团文章链接:http://tech.meituan.com/ac.html

时间: 2024-10-08 00:34:42

利用AC自动机进行关键字的提取和过滤的相关文章

hdu2243 ac自动机+矩阵连乘

http://acm.hdu.edu.cn/showproblem.php?pid=2243 Problem Description 背单词,始终是复习英语的重要环节.在荒废了3年大学生涯后,Lele也终于要开始背单词了. 一天,Lele在某本单词书上看到了一个根据词根来背单词的方法.比如"ab",放在单词前一般表示"相反,变坏,离去"等. 于是Lele想,如果背了N个词根,那这些词根到底会不会在单词里出现呢.更确切的描述是:长度不超过L,只由小写字母组成的,至少包

hdu 3695:Computer Virus on Planet Pandora(AC自动机,入门题)

Computer Virus on Planet Pandora Time Limit: 6000/2000 MS (Java/Others)    Memory Limit: 256000/128000 K (Java/Others)Total Submission(s): 2578    Accepted Submission(s): 713 Problem Description Aliens on planet Pandora also write computer programs l

AC自动机+状压DP

HDU 2825 题目大义: 给定m个长度不超过10的单词,你需要构造一个长度为n的文本串,文本串包含的给定单词不小于k个,求方案数. 分析: 看完题目觉得像是数学题,需要一些容斥啥的算算(其实是瞎想,不知道怎么实现),最后也只会暴力搜索判断这种没分数的写法.其实是一道DP题,算是一种利用AC自动机做DP的套路,需要学会. 定义F[i][j][mask],表示当前文本串长度为i,匹配到了AC自动机上的第j个节点,已包含的单词集合为mask时的方案数.显然我们可以从j节点向下更新,继续枚举一个l字

跳跃表,字典树(单词查找树,Trie树),后缀树,KMP算法,AC 自动机相关算法原理详细汇总

第一部分:跳跃表 本文将总结一种数据结构:跳跃表.前半部分跳跃表性质和操作的介绍直接摘自<让算法的效率跳起来--浅谈"跳跃表"的相关操作及其应用>上海市华东师范大学第二附属中学 魏冉.之后将附上跳跃表的源代码,以及本人对其的了解.难免有错误之处,希望指正,共同进步.谢谢. 跳跃表(Skip List)是1987年才诞生的一种崭新的数据结构,它在进行查找.插入.删除等操作时的期望时间复杂度均为O(logn),有着近乎替代平衡树的本领.而且最重要的一点,就是它的编程复杂度较同类

[C#] 逆袭——自制日刷千题的AC自动机攻克HDU OJ

前言 做过杭电.浙大或是北大等ACM题库的人一定对“刷题”不陌生,以杭电OJ为例:首先打开首页(http://acm.hdu.edu.cn/),然后登陆,接着找到“Online Exercise”下的“Problem Archive”,然后从众多题目中选择一个进行读题.构思.编程.然后提交.最后查看题解状态,如果AC了表示这一题被攻克了,否则就要重做了~一般情况下,“刷题”要求精神高度集中且经验丰富,否则很难成功AC,有时候甚至做一题要浪费半天的时间!(有时网速卡了,比抢火车票还要急!) 楼主在

【暖*墟】 #AC自动机# 多模式串的匹配运用

一.构建步骤 1.将所有模式串构建成 Trie 树 2.对 Trie 上所有节点构建前缀指针(类似kmp中的next数组) 3.利用前缀指针对主串进行匹配 AC自动机关键点一:trie字典树的构建过程 字典树的构建过程是这样的,当要插入许多单词的时候,我们要从前往后遍历整个字符串, 当我们发现当前要插入的字符其节点再先前已经建成,我们直接去考虑下一个字符即可, 当我们发现当前要插入的字符没有再其前一个字符所形成的树下没有自己的节点, 我们就要创建一个新节点来表示这个字符,接下往下遍历其他的字符.

AC自动机算法详解

首先简要介绍一下AC自动机:Aho-Corasick automation,该算法在1975年产生于贝尔实验室,是著名的多模匹配算法之一.一个常见的例子就是给出n个单词,再给出一段包含m个字符的文章,让你找出有多少个单词在文章里出现过.要搞懂AC自动机,先得有模式树(字典树)Trie和KMP模式匹配算法的基础知识.AC自动机算法分为3步:构造一棵Trie树,构造失败指针和模式匹配过程.     如果你对KMP算法和了解的话,应该知道KMP算法中的next函数(shift函数或者fail函数)是干

动手实现--AC自动机

Trie树: 把若干个单词按前缀合并就得到一棵树,这棵树称为Trie树.Trie树是有根树,每条边表示一个字符,每个节点表示一个从根到当前节点的唯一路径上的字符依次连接得到的字符串.由于空串是任何串的前缀,因此根就表示“空串”这个串.如何区分单词节点和非单词节点呢?插入单词的时候对每个节点mark一下即可. KMP算法思想:  能匹配就匹配,不能匹配就进行尽量小的平移来达到匹配. 有限自动机: 自动机是一个处理信息的机器,它的核心是状态和状态转移(和dp一样??),通过设计不同的状态和状态转移函

UVA 11468 - Substring(AC自动机)

UVA 11468 - Substring 题目链接 题意:给定一些模式串,然后给出一些字母出现的概率,每次随机出现一个字母,要求出这些字母出现L个组成的字符串不包含(即不是它的连续字串)已有模式串的概率 思路:AC自动机,先构造出AC自动机,构造的时候利用next数组的特性,记录下每个位置是否有经过一个单词结点,如果有这个结点就是不能走的结点,那么问题就变成了只能在能走的结点上走L步的概率,注意这里空边也要处理成可以走(走到它的next,因为不匹配的话就一直找到next能匹配的位置),然后进行