相关文章
KMP与AC自动机详细讲解(带图)
2024-11-10 18:49

KMP​ 算法可以说是我学过的算法里最让我印象深刻的一个算法了。初学 KMP​​ 的时候真的是抓耳挠腮,硬啃了一下午的博客才勉强可以自己独立推一遍算法的整个流程。第二次学习 KMP​ 是为了在数据结构课上给同学们介绍这个算法,自己学和教会别人又是不一样的难度,于是我又重新学习了一遍,但这一次学习时有很多之前觉得很抽象的东西都突然茅塞顿开了,为了讲解的效果,我还反复推导了几次算法,确保讲课的流畅。第三次学习 KMP​ 是为了给集训队的学弟们讲这个算法,而竞赛更偏重于算法的应用,所以我在重新推演了一次算法后又找了一些经典例题。自此,对于 KMP 的理解可以说是挺明晰了。最近,我又学习了 AC自动机,很巧的是,AC自动机的思想和 KMP 是一样的,于是我又“被迫”重温了一遍 KMP ,既然那么有缘分,不如就写篇博客吧。

KMP与AC自动机详细讲解(带图)

KMP算法(又称看毛片算法),名字取自它的三个共同发明者名字的首字母,是一个高效率的字符串匹配算法。主要解决这样一个问题:有一个 S 串(称为匹配串)和一个 P 串(称为模式串 pattern​),问在 S 中是否出现过 P 以及出现过几次的问题。

首先我们可以想到一个不计效率的暴力做法:将 S 串的 i​ 位置作为起点与 P 串进行比较,如果整个字符串匹配了则退出,如果在某个位置失配了,则 Si+1 开始作为起点与整个 P 串重新比较,直到 S 串的每个位置都与 P 比较过后结束,假设 S 串长度为 nP 串长度为 m ,则整体的时间复杂度为 O(nm),上述流程的代码如下:

上面整个暴力做法效率太低了,我们考虑如何进行优化?我们在模拟暴力的匹配过程中可以发下,每次失配后 i 指针和 j​​​ 指针都要回退很大的一段长度,然后重新比较,我们如果可以根据已有的信息通过理论分析来减少回退的距离那么就能提高匹配的效率了, KMP 算法就是这样去考虑的。其实 KMP​ 本质就是一个设计精妙的动态规划。

假设我们某次匹配失败了,如下图所示,绿色部分表示匹配成功的部分,在 “x” 处匹配失败。假如 P 串的某个前缀子串和后缀子串相同(红色部分),那么我们就可以利用这个信息减少回退。

由于绿色部分是相同的,很容易可知,S 串对应的红色部分也和 P 串的两个红色部分相同,我们可以将 S 不动,P串向后移动直到 S 的红色部分和 P​ 串的前半个红色对齐,然后继续比较下一个位置是否相同即可,如下图所示:

既然如此,那么我们如果可以找到 P​ 串最长的红色部分,那么我们就可以让 P​​​ 串移动的距离最少使得效率最大化。下面证明一下此结论的正确性:假设某次匹配失败后(图中绿色部分),P串向后移动了一段最短的距离,并成功匹配,如图中红色部分。由于红色部分一一对应相等,必然可得蓝色部分也相等,我们可以发现在 S​ 串中,蓝色部分是绿色部分的后缀,在 P​ 串中蓝色部分是绿色部分的前缀,那么要让 P 串移动的距离最小,其实就是让蓝色部分尽量长,即让绿色部分的前缀和后缀相同且尽量长。

好了,现在我们已经找到优化匹配过程的方法了,就剩下一个问题:如何求得 P​ 串的某个前缀子串的最长的相同的前缀和后缀的长度(有点拗口,多读几遍理解)呢?这就是 KMP 的精妙之处,也是初学者最难理解的部分:next 数组。

2.3.1 什么是next数组?

因为无法知道 P​​​ 的那些子串需要用到最长的相同的前缀后缀长度,所以最稳妥的方法就是预处理出P串的每个前缀子串的最长的相同的前缀后缀长度。于是引出 next​​ 数组的定义:next[i]​​ 表示 P​​ 串下标为 **0​ 到 i-1​​ **的子串 x​​ 的某个前缀与 x​ 的后缀相同,记录最长的长度(注意记录的是0到 i-1 的子串!!),另外这里的前缀和后缀必须都是非平凡的(即不包括自身),比如abcd的前缀只有a,ab,abc,后缀只有d,cd,bcd,后面描述中的前缀后缀都是非平凡的,不再重复。

举个例子:

2.3.2 怎么求next数组?

明确next数组的定义后,思考如何求解next数组。

如图,假设计算 next[i+1] 时某个时候,发现 p[i] neq p[j] ,我们就要考虑有没有更小的一个前缀可以和后缀匹配,假设子串 p[0…j-1]​ 中存在一个最长的前缀和后缀相等(蓝色部分),那么由于绿色部分相等,我们可以推得,绿色中也有一部分和前缀的蓝色相同,那么我们只要比较p[k]p[i]​ 是否相等,如果不相等继续找 k 之前子串的最长相等前缀后缀,直到发现相等或者 k 到边界0,这样就能求得 next[i+1]​ 。

我们发现 j​​​ 指针的转移依据是下标为0到j-1的子串的最长前缀后缀,即通过 j = next[j]​​ 不断向前移动 j​ 指针,直到发生匹配记录相应的 next​ 值,或者 j = 0​ 时也失配则结束寻找相应的 next = 0​,由于要用到之前的 next​ 信息,所以我们可以用递推的方式从小往大求得所有的next值,注意边界 next[0] = -1 ,这是特殊规定的,但也有它的巧妙之处,先给出代码:

代码可能有点地方不太容易理解,主要就是下标的变换,这也是我初学 KMP​ 时最头疼的一个地方,下面对主要部分进行分析,但最好还是手动模拟逐一理解:

现在我们有 next​ 数组了,那么我们再回头将 KMP​​ 代码化吧!如果忘记了怎么用 next 优化匹配的话,可以往前翻重新回顾一下。下面直接给出 KMP​ 的核心代码:

我们可以发现, KMP​ 的写法几乎和 next​ 的求解一模一样,这里就不做过多分析,讲解一下大致流程:首先 ij 都指向起点 0 ,进入循环,如果两者匹配,则同时+1,继续匹配下一个字符,如果发现失配,那么就将 j 指向 next[j] ,同时由于下标从0开始,所以指向的也正好是下一个待匹配的位置,继续匹配,如果一直失配则会遇到边界 next[0] = -1​ ,则将i+1,j+1 ,即i进入下一个位置和 j = 0 比较。循环的边界是 ij 走到字符串外面,如果 j​ 走到字符串外面了,说明 S 串中存在匹配。

分析代码,我们可以发现匹配的过程中 i​ 是不会往回退的,j​ 虽然会回退,但也是有限的几次可以忽略,所以这里的复杂度为 O(n),另外还有求 next 数组,同理可得复杂度为 O(m),故总体复杂度为 O(n+m)

KMP将一个乘法级别的复杂度降低到了加法级别,可以说是巨大的提升,那么是否还有可以优化的地方呢?

观察下面这个情况:

我们发现 s[3] neq p[3]​ ,则令 j = next[3] = 1​ ,即比较 s[3]​ 和 p[1]​ 这必然是失配的,因为一开始 s[3] neq p[3] = ‘b’​,而

p[1]​​​ 也等于‘b’,所以必然失配,对于这种可以推得的“必然失配”的情况,我们都可以将其优化掉。考虑什么时候会出现这的情况:只有当p[j] = p[next[j]]​ 时才会出现上述的情况,那么当 p[j] = p[next[j]] 是,我们只要让 next[j] = next[next[j]] 就行了,这部分的优化可以在 next 数组的求解中完成,优化后的代码如下:

AC自动机顾名思义就是自动AC的机器,可以帮助你将难题直接Accept掉,有些东西还真不能顾名思义,AC自动机全称为Aho-Corasick automaton,该算法在1975年产生于贝尔实验室,是著名的多模匹配算法。所谓多模匹配算法,最常见的例子是给出n个单词,再给出一段包含m个字符的文章,让你找出有多少个单词在文章里出现过。要学习AC自动机,首先需要先学会 Trie(字典树),没了解过的可以先去了解一下,很简单的一种数据结构,下文都建立在掌握字典树的基础上展开。AC自动机算法和KMP很像,一共分为三步:构造一棵 Trie 树,构造失败指针和模式匹配过程。

假设现在有四个单词:、 、 、 ,我们可得下面这样的一颗 Trie树:

我们定义一个 fail​ 指针:对于某个节点 p​ ,它的 fail​ 指针指向某个节点 q​​​ ,我们令:

q​ 节点满足,S​​ 的最长的非平凡后缀(即不包括自身的后缀)与 P​​ 串相等,如果不存在这样一个点 q ,则 fail 指向根节点,据此我们可以画出上图的字典树中每个节点的 fail​ 指针:

上面是 fail​ 指针的抽象定义,其主要功能就是在匹配串匹配失败后,将指针转移到 fail​ 指针指向的地方,这样就不用回溯,而可以一路匹配下去了。其实 fail​ 指针指向的就是当前搜索的串的后缀可以匹配的所有以根节点为起点的子串的前缀的最大值,假设我们有一个匹配串 S​ 在匹配的过程中的某个位置发生失配了,那么以失配位置为结尾的这段字符串的一部分有可能成为某个单词(匹配串)的前缀,所以我们跳到最大的前缀继续比较。

了解了 fail​​​ 指针的定义,我们进行文本串的匹配,假设有个匹配串 ashex,我们沿着字段树向下搜索(蓝色箭头为路径):

一直沿着字典树向下走,直到发现走到一个绿色节点,说明找到了某个单词(字典树中如果某个节点为某个单词的最后一个字符,则会标记这个节点,图中以绿色为标记),此时 ‘h’ 没有后续节点,匹配失败,我们通过最左边的这个 ‘h’ 的 fail​ 指针,转移到了中间的这个 ‘h’ ,然后继续向下走,走到 ‘x’ 后又发现绿色节点,则又找到一个单词。此时’x’没有后续节点,匹配失败,则通过 fail​​ 指针转移,走到 root 节点,匹配结束,整个过程发现两个单词。

以 AcWing 1282. 搜索关键词 为例,给出模板:

由于这道题收录在付费课程中,可能无法查看题目,这里直接给出题目(强推Acwing的算法系列课程,真的很棒!):

1282.搜索关键词

给定 n 个长度不超过 50 的由小写英文字母组成的单词,以及一篇长为 m 的文章。

请问,有多少个单词在文章中出现了。

输入格式

第一行包含整数 T,表示共有 T 组测试数据。

对于每组数据,第一行一个整数 n,接下去 n 行表示 n 个单词,最后一行输入一个字符串,表示文章。

输出格式

对于每组数据,输出一个占一行的整数,表示有多少个单词在文章中出现。

数据范围

1≤n≤10^4,

1≤m≤10^6

输入样例:
输出样例:

代码

可以类比 KMP 其实也是用递推的思想,即使用上面几层的节点求下面的 fail​​ 指针,由于按层求,所以用bfs遍历,具体看代码吧,解释起来有点麻烦,代码详解先鸽着,有空的话再补充。

KMP 一样,fail​ 指针也有相应的优化,在计算 fail 指针时这样进行:

继续鸽,有空再解释

(v_JULY_v大佬的超详细博客,第一次接触kmp就啃的他的博客)

(y老师的模板,十分简短)

    以上就是本篇文章【KMP与AC自动机详细讲解(带图)】的全部内容了,欢迎阅览 ! 文章地址:http://ktsh.xhstdz.com/news/6381.html 
     栏目首页      相关文章      动态      同类文章      热门文章      网站地图      返回首页 物流园资讯移动站 http://ktsh.xhstdz.com/mobile/ , 查看更多   
最新文章
【优化求解】遗传算法求解岛屿物资补给优化问题【含Matlab源码 172期】
🚅座右铭:行百里者,半于九十。 🏆代码获取方式: CSDN Matlab武动乾坤—代码获取方式 更多Ma
竞争优势利器:利用推广排名优化碾压同行,脱颖而出
在竞争激烈的市场环境中,企业想要脱颖而出,获得竞争优势,至关重要。推广排名优化正是数字化时代下帮助企业实现这一目标的重要
药师解药 | 妊娠期胰岛素过敏怎么办,教你几招来应对!
据相关研究统计,胰岛素和胰岛素类似物在治疗中出现过敏反应的概率为0.1%到7.1%不等,注射部位反应发生率约为1.4%。胰岛素过敏原
圣邦微最新动态与技术发展,深度解析与SEO优化文章,圣邦微最新动态与技术发展深度解析及SEO优化攻略
本文关注圣邦微的最新动态与技术发展,提供深度解析并针对SEO优化。文章将详细介绍圣邦微的最新技术进展、产品更新以及市场策略
成品网站1.1.719:如何高效搭建企业与个人网站,提升用户体验与功能性能
成品网站1.1.719是一个针对网站开发和建站需求的产品版本,它为企业和个人用户提供了一个简单易用的解决方案,帮助他们快速搭建
文心一言APP无法连接网络
文心一言APP无法连接网络许多用户反映,他们所喜爱的文心一言APP无法连接网络。这款APP以其精选的古代文言文名句和现代文学名篇
谷歌收录秘籍:揭秘提交入口网址
谷歌收录提交入口:专业指南与重要性解析在当今数字化时代,互联网已成为信息传播与商业活动的重要平台对于网站运营者而言,确保
浅探webpack优化
由于前端的快速发展,相关工具的发展速度也是相当迅猛,各大框架例如vue,react都有自己优秀的脚手架工具来帮助我们
百度收录平台排名 揭秘百度收录平台排名,优化策略助你领跑新媒体
在当今这个信息爆炸的时代,互联网已成为企业展示自我、吸引客户、拓展市场不可或缺的重要渠道而在浩瀚的网络海洋中,如何让自己
为什么要做海外推广方式
为什么要进行海外推广方式:探索国际化市场的关键路径一、引言在全球化和数字化迅猛发展的当下,企业的海外推广成为了进入国际市
相关文章