一、概念
- 多模式串匹配:提供一个主串,多个模式串,在一个主串中找多个模式串,例如在一个文本中过滤多个敏感词就属于这个范畴
- AC自动机就是一种多模式串匹配算法,原名Aho-Corasick automaton
二、思路
(一) 、构建tire树,将多个模式串放入tire树中,与一般tire树不同的是,每个tire树多存一个模式串的长度和每个节点的失效指针,具体可见代码
(二)、广度优先遍历tire树构建失效指针,何为失效指针,就是主串沿着tire树遍历,当遍历到tire树叶子节点走不下去的时候,就沿着tire树叶子节点的失效指针的指向继续走,如何构建失效指针呢?
1)首先要明白,失效指针的查找过程就是找匹配的最长后缀,如head节点走到下图中she的e中,she的后缀有e,he,那最长就是找he,那图中Hers中就有he,在遍历的过程中怎么知道有个hers,其中就有个he呢?这就要用到父节点
2)e的父节点是h,那sh的最长后缀就是h,sh的h怎么找到hers的h呢,就要用到s,通过h找到s,在通过s找到head,head有一个子节点为h,e的失效指针查找过程同样如此,知道h指向hers的h,判断hers的h有没有e的子节点,如果有,e就指向hers的e,没有就指向头节点
(三)、匹配过程:
1)如模式串是her,his,she,he,主串是mhishers,模式串构建的tire树如下,图中的黑色虚线表示每个节点的失效指针指向,我们来遍历一遍主串
2)首先是m,头节点指向的s、h,没有m,直接跳过
3)然后是h,走tire树s分支,然后是i,走i分支,然后是s,找到第一个单词his了,可以通过节点中的长度把下标打印出来,继续往下走是h,因为s没有节点为s,即这时候就走不下去,这就要用到失效指针
4)看图中指向,s的失效指针是head下s,就沿着head下s继续往下走,依次类推
三、代码实现
package StringMatch;
import java.util.LinkedList;
import java.util.Queue;
/*
AC自动机:多模式串匹配算法
思路:Tire树+KMP
单纯的用tire树也可以进行多模式串匹配,把模式串全部放入Tire树中,多次用主串遍历tire树
而AC自动机只需遍历一遍主串就可完成多模式串匹配,只有在极端情况下每次,失效指针每次都指向头节点,AC自动机的性能才会退化和Tire树一样
*/
public class AcAuto_zhu {
private static final node head = new node('/');
static class node{
private char data;
private int length = -1;
private boolean isEndgingChar = false;
private node[] children = new node[26];
private node fail;
public node(char data){
this.data = data;
}
}
private static void insert(String str){
char[] text = str.toCharArray();
node p = head;
for(int i = 0; i < text.length; i++){
int index = text[i] - 'a';
if (p.children[index] == null){
p.children[index] = new node(text[i]);
}
p = p.children[index];
}
p.isEndgingChar = true;
p.length = text.length;
}
//广度优先遍历tire树构建失效指针
private static void buildFail(){
Queue<node> queue = new LinkedList<>();
head.fail = null;
queue.add(head);
while(!queue.isEmpty()){
node p = queue.remove();
for (int i = 0; i < 26; i++){
node q = p.children[i];
if (q == null){
continue;
}
if (p == head){
q.fail = head;
}else{
node r = p.fail;
while (r != null){
node m = r.children[q.data - 'a'];
if (m != null){
q.fail = m;
break;
}
r = r.fail;
}
if (r == null){
q.fail = head;
}
}
queue.add(q);
}
}
}
public static void match(String mainString,String...args){
char[] main = mainString.toCharArray();
for (String s : args){
insert(s);
}
buildFail();
node p = head;
for(int i = 0; i < main.length; i++){
int index = main[i] - 'a';
if (p.children[index] == null && p != head){
p = p.fail;
}
p = p.children[index];
if (p == null){
p = head;
}
node tmp = p;
while (tmp != head){
if (tmp.isEndgingChar == true){
int pos = i - tmp.length + 1;
System.out.println("下标为:" + pos + "长度为:" + tmp.length);
}
tmp = tmp.fail;
}
}
}
public static void main(String[] args) {
String main = "ahishers";
match(main,"hers","his","she","he");
}
}