AC自动机

简介

KMP 算法是单模式串的字符匹配算法,AC自动机是多模式串的字符匹配算法。
Trie 树是一种哈希树的变种。
AC自动机算法分为3步:
  ① 构造一棵Trie树;
  ② 构造失败指针;
  ③ 模式匹配过程。

结构体

typedef struct A {
	int cent;//记录是否为尾节点 
	A *next[26];//子节点指针 
	A *fail;//失败指针 
	A(){//初始化 
		cent = 0, ms(next), fail = NULL;
	}
}node;

1. 构造一棵Trie树

  假设我们有下面的单词,she , he ,say, her, shr ,我们要构建如下一棵字典树:

在这里插入图片描述

//插入一个子串的代码如下:
void Build_Tiree(string s)//建字典树 
{
	node* q=root;
	for(int i=0;i<s.length();i++){//将字符串插入 
		int k=s[i]-'a';
		if(q->next[k]==NULL)//如果没有分配空间 
			q->next[k]=new node();//创建空间 
		q=q->next[k];
	}
	q->cent++;
}

2. 构造失败指针

构造失败指针的过程

  设这个节点上的字母为C,沿着它父亲节点的fail指针走,直到走到一个节点,它的子节点中也有字母为C的节点。然后把当前节点的fail指针指向那个字母也为C的儿子。如果一直走到了root都没找到,那就把失败指针指向root。
  具体操作起来只需要:先把root加入队列(root的失败指针指向自己或者NULL),这以后我们每处理一个点,就把它的所有儿子加入队列。

构造失败指针的流程

  ① 首先root节点的fail指针指向NULL,然后root入队,进入循环。
  ② 从队列中弹出root,root节点与h、s节点相连,因为它们是第一层的字符,肯定没有比它层数更小的共同前后缀,所以把这2个节点的fail指针指向root,并且先后进入队列,fail指针的指向对应图中的两条红线;
  ③ 从队列中弹出h(左边那个),h节点所连的只有e结点,所以接下来扫描指针指向e节点的父节点h节点的fail指针指向的节点,也就是root,root->next[‘e’] == NULL,并且root->fail == NULL,说明匹配序列为空,则把节点e的fail指针指向root,对应图中的蓝色,然后节点e进入队列;
  ④ 从队列中弹出s,s节点与a,h(左边那个)相连,先遍历到a节点,扫描指针指向a节点的父节点s节点的fail指针指向的节点,也就是root,root->next[‘a’] == NULL,并且root->fail == NULL,说明匹配序列为空,则把节点a的fail指针指向root,对应图中的蓝色,然后节点a进入队列。
  ⑤ 接着遍历到h节点,扫描指针指向h节点的父节点s节点的fail指针指向的节点,也就是root,root->next[‘h’] != NULL,所以把节点h的fail指针指向右边那个h,对应图中的蓝色,然后节点h进入队列…由此类推,最终失配指针如图所示。

在这里插入图片描述

void Build_AC_Tiree()//初始化 fail指针 
{
	queue<node*>q;
	q.push(root);//根结点入队 
	while(!q.empty())
	{
		node *re=q.front();//头节点出队 
		q.pop();//删除 
		for(int i=0;i<26;i++)//遍历子节点 
		{
			if(re->next[i]!=NULL)//判断,是否存在此子节点 
			{
				if(re==root) //特判是否为根节点 
					re->next[i]->fail=root;
				else
				{
					node *p=re->fail;
					while(p!=NULL)//向上寻找失败指针 
					{
						if(p->next[i]!=NULL) 
						{
							re->next[i]->fail=p->next[i];
							break;
						}
						p=p->fail;
					}
					if(p==NULL)//为空直接指向root 
						re->next[i]->fail=root;
				}
				q.push(re->next[i]);
			}
		}
	}
}

3. 模式匹配过程

我们便可以在AC自动机上查找模式串中出现过哪些单词了。匹配过程分两种情况:

  ① 当前字符匹配,表示从当前节点沿着树边有一条路径可以到达目标字符,此时只需沿该路径走向下一个节点继续匹配即可,目标字符串指针移向下个字符继续匹配;
  ② 当前字符不匹配,则去当前节点失败指针所指向的字符继续匹配,匹配过程随着指针指向root结束。重复这2个过程中的任意一个,直到模式串走到结尾为止。

对例子来说:其中模式串为yasherhs。

  ① 对于I=0,1时。Trie中没有对应的路径,故不做任何操作;
  ② 当 i=2,3,4时,指针p走到左下节点e。因为节点e的cent信息为1,所以ans+1,并且将节点e的cent值设置为-1,表示改单词已经出现过了,防止重复计数,最后temp指向e节点的失败指针所指向的节点继续查找,以此类推,最后temp指向root,退出while循环,这个过程中ans增加了2。表示找到了2个单词she和he。
  ③ 当i=5时,程序进入第5行,p指向其失败指针的节点,也就是右边那个e节点,随后在第6行指向r节点,r节点的cent值为1,从而ans+1,循环直到temp指向root为止。最后i=6,7时,找不到任何匹配,匹配过程结束。

int query(string s)
{
	
	int ans=0;
	node *p=root;
	for(int i=0;i<s.length();i++)
	{
		int k=s[i]-'a';
		while(p->next[k]==NULL&&p!=root)//如果p子节点k不存在,那表明匹配失败 
			p=p->fail;//找失败节点 
		p=p->next[k];//指向下一个节点 
		if(p==NULL)//特判下父节点是不是根节点 
			p=root;
		node *temp=p;
		while(temp!=root&&temp->cent>=0)//这个是找这个子串的其他子串 
		{
			ans+=temp->cent;
			temp->cent=-1;
			temp=temp->fail;
		}
	}
	return ans;
}

例题:HDU 2222

1
5
she
he
say
shr
her
yasherhs

3

  1. AC自动机讲解
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值