AC自动机 —— 多模式匹配 模板、个人理解及详细注释

解决单个字符串匹配的方法是KMP算法

得益于其next数组,整个算法时间复杂度被显著压缩。

在O(n+m)的时间内即可以算出模式匹配。

 

AC自动机是一个支持多模式匹配的算法。

也就是给出模式串A1,A2,A3……An,以及一个字符集S

模式串长度不大的情况下算出匹配数

有N个模式串,平均长度为L;文章长度为M。

立Trie树:O(N*L)

建立fail指针:O(N*L)

模式匹配:O(M*L) 所以,总时间复杂度为:O( (N+M)*L )。

 

【注:有时记得考虑字符范围常数】

 

 

便于理解,可以考虑类似于优化过的暴力算法

譬如在abcaabacc找acc

除了长度优化以外,最直接的优化方法:“只找a

算是一个残废的KMP的next数组。

 

个人认为是理解失败指针的一个不错的方式



#include <bits/stdc++.h>
using namespace std;

const int maxn = 50;
struct TrieNode;
typedef TrieNode* Position;
//字典树节点 
struct TrieNode{
	Position next[maxn]; //next数组,字典树概念 
	Position fail;		 //失败指针,自动机新节点 
	int sum;			//某道题需要存的值 
	TrieNode():fail(0),sum(0){
		memset(next,0,sizeof(next)); //初始化 
	}
};

struct Trie{
	inline static int toNumber(char t){
		return t - 'a';    //把字符映射成数字, 
	}
	
	Position root; 
	void insert(const char* s){ //插入函数 
		if(!root)				//根为空,给根开辟空间 
			root = new TrieNode();	
		Position head = root;		//迭代向下走,走不了开辟新节点 
		for (int i = 0 ; s[i] ; ++i){
			int next_pos = Trie::toNumber(s[i]);
			if(!head->next[next_pos])
				head->next[next_pos] = new TrieNode(); 
			head = head->next[next_pos];
		}
		head->sum++; //打个标记,意味着如果到这里就代表有个模式在这里匹配 
	}
	//插入操作类似于字典树,仅有微小区别。 
	
	
	//以下是AC自动机核心部分,建立fail指针节点,利用BFS 
	void build_AC(){
		Position tmp;
		Position p;
		queue<Position> Q; 	//队列 
		Q.push(root);		//根入队 
		while(!Q.empty()){
			tmp = Q.front(); Q.pop();	//基本操作 
			for (int i = 0 ; i < maxn ; ++i){
				if(tmp->next[i])
				{
					if(tmp == root)		//1.所有根节点的儿子节点的失败节点指向根 
						tmp->next[i]->fail = root;	//因为一旦从这里匹配失败了自然只能从头再来 
					else
					{
						/*
							2.如果不是根节点:
							大致意思是: 
							  在某个地方不匹配,就要找到另外一个有可能走的地方
							  确实,能走的地方(当前匹配)有很多,考虑由底向上
							  例子:
							  		abababa
									0123456
									在位置6的a的失败指针应该指向位置4的a的下一个地方
									在位置4的a的失败指针应该指向位置2的a的下一个地方
									以此类推。。。。
									这样一来,如果位置6的不匹配,首先走到4,然后再走到2,走到0
									顺然而然直到匹配位置,或是回到根节点 
						
						*/
						
						p = tmp->fail;
						while(p)
						{
							if(p->next[i])
							{
								tmp->next[i] -> fail = p->next[i];
								break;
							}
							p = p->fail;
						}
						if(p == NULL)
							tmp->next[i]->fail = root; //如果找不到,只能从头再来。 
					}
					Q.push(tmp->next[i]);	//基本操作 
				}
			}
		}
	}
	
	//匹配部分,可能比上述好理解 
	int Ac_automation(const char* t){
		int cnt = 0;	 //统计匹配数目,可忽略,随题变化 
		Position p = root;	
		int len = strlen(t);	
		for (int i = 0 ; i < len ; ++i){
			int next_pos = Trie::toNumber(t[i]);	//遍历字典树,看下一个是否存在 
			while(!p->next[next_pos] && p != root) p = p->fail;
			//不行就一直沿着失败指针走,走到根节点为止 
			
			p = p->next[next_pos]; //1.找到了  2.回到根节点  
								   //无论如何,按字典树走一步 
			
			
			
							// p为空的情况 也就是说,在上一步啥也没找到,回到根,根往后强行走一步
			if(!p)			//到了空指针处,把p修正为根,重新开始 
				p = root;
				
			Position tmp = p;
			while(tmp != root)
			{
				if(tmp->sum >= 0)
				{
					cnt += tmp->sum;
				}
				else break;
				tmp = tmp -> fail;
				
				//一句话就是匹配了一个串等于匹配了它所有的前缀。
				//匹配到abababab 也就匹配到 ababab 也就匹配到 ababa , aba , ab 。.....
				//可以自行改 
				 
			}
		}
		return cnt;			
	}
}; 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值