KMP算法
KMP算法
介绍:
KMP算法是一种字符串匹配算法,用来高效匹配字符串子串问题。- 一般的做法就是一个一个试,时间复杂度为O(n * m),这太大了,因此我们在大规模字符串的匹配时我们使用
KMP算法就能将时间复杂度降到O(n+m),这是显著的提升。 KMP算法里面会涉及许多其他概念,如前缀表(或next表),最长相同前后缀等概念,后面会从最基础的概念开始,一步一步讲解这里面的概念。- 如果下面的讲解没看懂,也可以先把概念了解了,去看看B站上面的视频演示,视频加博客的学习可以加深印象。
- 参考视频:最浅显易懂的 KMP 算法讲解 ,跟人认为这个视频还是比较优质的,本篇博客的写作思路要源于此。
概念解释:
KMP算法的思想:
- 我们期望不同于传统方法不断回退
i来达到匹配字符串的目的,因此我们KMP算法里面i是不回退的,一直往后走,那我们又该怎么实现匹配呢? - 既然我们的
i不会退但不代表我们的j不能回退,因此我们要找到一种回退方法,来使得我们的回退再匹配是有意义的,这就引出了我们的KMP算法里面的前缀表。
前后缀:
前缀就是从开头开始的连续子串,不包括空字符串和整个字符串。后缀就是从末尾开始的连续子串,不包括包括空字符串和整个字符串。最长相同前后缀:字面意思,就是我们要找到所有前后缀里面,最长且相等的那一个(后面讲解递推求法)
前缀表:
- 我们的匹配字符串里面的每个位置上都有一个以其为结尾的
最长相同前后缀的长度 1
2
3
4
5
6
7
8aabaa
01012 这就是一个前缀表
只有一个a时,没有前后缀
aa 前后缀最长且相等为1,都是a
aab 显然没有相等的前后缀 ,为 0
aaba 通过观察得到,前后都有一个a,所以为 1
aabaa 前后缀里面,aa和aa完全相同,所以为 2
因此就生成了我们aabaa的前缀表
详细实现细节:
原理讲解:
假如我们已经有了前缀表
- 我们在匹配时,就可以一直往前走,根据我们m的前缀表来对匹配字符串进行跳转
1 | |
- 通过对前面的分析,我们可以发现,我们的
i一直在往后面跳转,没有回退,只通过前缀表的跳转来实现pattern的挪动
该部分演示代码 :
1 | |
- 这里面的代码思路就是我们上面的讲解思路。
- 下一个问题就是实现前缀表的生成。
生成前缀表
- 我们要知道,前缀表的生成是一个求前后缀的过程,从第一个字母开始,到后面最后一个位置,每移动一个位置,就求一遍前面所有的字母的最长相等前后缀
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21讲解如下:
假如给定一个字符串,求其前缀表,我们非暴力求解过程如下(暴力求解就是定义双指针一个一个试):
依旧是例题代码:
aabaa 以这个为例
当长度为1的时候,不存在前后缀,profix[0]=0
所有长度为1时,直接可以默认为0,从2开始,后面的都通过一套流程来生成
依旧经典双指针,一个指引现在我们pattern的位置,一个指引前缀
curlen代表现在匹配的前后缀的长度,i对应我们现在指引的位置(用for来实现)
curlen初始定义为0
i从1开始
接下来,先看patten[i]和pattern[curlen]匹不匹配,分为下面两种情况
匹配:就把curlen++,并且profix[i]=curlen,注意这里要先加在赋值,索引问题
不匹配:判断时候为0,如果为0,就证明一点都没法匹配,profix[i]=0
444如果不为0,我们就往前推,curlen=profix[curlen-1]
444为何呢?
我们已经推出了前面的前缀表,因此curlen前一个位置的前缀长我们是知道的,
我们通过查表的方式,就可以将当前curlen的位置,跳转到和curlen目前长度里面前后缀相同的地方的下一 个,来个例子演示一下
abcabacdaabaa 假设当前i到了第12个位置
00012100112 curlen当前也到了第3个位置
发现下一个刚好不能匹配 于是我们来跳转,curlen=profix[curlen-1] curlen=2
跳转后我们发现当前curlen前面的部分和i前面的部分是完全相同的,为什么呢,因为本身前缀表里面就代表 的是前后缀关系,我们跳转后就发现前后缀就直接对上了,这大大提高了我们的查找效率,我们按照这样的顺 序一个一个往后推,知道前缀表的生成
代码模板:
1 | |
- 本题也是洛谷KMP模板题的答案。
- 对应题链接:P3375 【模板】KMP - 洛谷
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 GuMing的殿堂!
评论


