ac自动机详解(用失配指针实现多模字符串匹配)

12 篇文章 0 订阅
2 篇文章 0 订阅

ac自动机,是用于计算多个字符串匹配的算法,我们知道, K M P KMP KMP以一种及其巧妙的方式实现了以单模字符串的匹配(以单个字符串去询问其他字符串中是否出现该字符串),现在我们要实现多模的字符串匹配(即以多个字符串去询问其他字符串中是否出现了这些字符串),用 K M P KMP KMP显然是不足够的,那么就需要一种新的算法继承了 K M P KMP KMP的思想,还要建立在一个数据结构上,它便是 t r i e trie trie字典树。
字典树是将字符串分解为单个字符并存在树上的一种数据结构,例如,我们有
{“ab”,“ac”,“ba”,“cba”}四个字符串,我们便可以构建如下的一棵树来存储这些字符串。
在这里插入图片描述
p s : ps: ps:由于是前置知识,在这里便不多赘述。

下面,我们该如何将这些字符串与另外的字符串如 c b b a cbba cbba匹配呢?我们可以暴力去搜树,但这样会浪费巨量的时间和空间,于是我们引入了这样的一个概念失配指针,例如我们去查数时,cb首先在最右边的子树上查到了,但是a却和字符串中的b不相匹配,重新查又觉得很浪费,这时我们看到中间的子树有个b,我们便可以跳转到中间的子树继续向下查询,由此推广,我们每次看到附近自根伸出的子树与当前正在查询的子树构成字符串的最大后缀相等时,我们便可以通过失配指针跳转到后来的子树继续查询来最大限度的避免浪费,由于后面的失配指针肯定是需要前面的失配指针来得到的,所以我们考虑用队列来完成每个节点失配指针的获取,具体如图所示:
在这里插入图片描述
下面给出一道模板题和ac代码:
题目传送门:hdu2222 Keywords Search
a c ac ac代码:

#include<bits/stdc++.h>

using namespace std;

#define MAX 1000009
#define TOTAL 500009 

struct Aho{
	struct state{
		int next[26];
		int fail,cnt;
	}stateTable[TOTAL];
	int size; 
	queue<int>q;
	void init(){
		for(int i=0;i<TOTAL;i++){
			memset(stateTable[i].next,0,sizeof stateTable[i].next);
			stateTable[i].fail=stateTable[i].cnt=0;
		}
		size = 1;
	}
	void insert(char *S){
		int n = strlen(S);
		int now = 0;
		for(int i=0;i<n;++i){
			char c = S[i];
			if(!stateTable[now].next[c-'a'])
			    stateTable[now].next[c-'a'] = size++;
			now = stateTable[now].next[c-'a'];
		}
		stateTable[now].cnt++;
	}
	void build(){
		stateTable[0].fail = -1;
		q.push(0);
				
		while(q.size()){
			int u = q.front();
			q.pop();
			for(int i=0;i<26;++i){
				if(stateTable[u].next[i]){
					if(u == 0) stateTable[stateTable[u].next[i]].fail = 0;
					else{
						int v = stateTable[u].fail;
						while(v!=-1){
							if(stateTable[v].next[i]){
								stateTable[stateTable[u].next[i]].fail = stateTable[v].next[i];
								break;
							}
							v = stateTable[v].fail;
						}
						if(v == -1) stateTable[stateTable[u].next[i]].fail = 0;
					}
					q.push(stateTable[u].next[i]);
				}
			}
		}
	}
	
	int Get(int u){
		int res = 0;
		while(u){
			res = res + stateTable[u].cnt;
			stateTable[u].cnt = 0;
			u = stateTable[u].fail;
		}
		return res;
	}
	
	int match(char *S){
		int n = strlen(S);
		int res = 0, now = 0;
		for(int i=0;i<n;++i){
			char c =S[i];
			if(stateTable[now].next[c-'a']){
			    now = stateTable[now].next[c-'a'];				
			}
			else{
				int p = stateTable[now].fail;
				while(p!=-1&&stateTable[p].next[c-'a']==0) p = stateTable[p].fail;
				if(p==-1) now = 0;
				else now = stateTable[p].next[c-'a'];
			}    
			if(stateTable[now].cnt){
				res += Get(now);
			}
		}
		return res;
	}
}aho;

char S[MAX];

int main(){
	int T;
	scanf("%d",&T);
	while(T--){
		aho.init();
		int n;scanf("%d",&n);
		for(int i=0;i<n;++i){
			scanf("%s",S);
			aho.insert(S);
		}
		aho.build();
		scanf("%s",S);
		printf("%d\n",aho.match(S));
	}
	
	
	return 0;
}

以上(什么嘛,还是很简单的嘛( ̄▽ ̄)/)

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

某六十九岁合法萝莉

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值