KMP算法名字由来
'KMP'名字并没有意义,由Donald Knuth,James Morris,Vaughan Pratt三人lastname的首字母组成。
KMP算法的功能,解决的问题
字符串匹配,word里的查找。
算法思想:
首先回顾暴力做法,其实很多算法都是基于暴力做法的改进:
其实暴力做法的过程中,本身就蕴含着前后缀
举个例子:
//---------h和q处失配
失配后,按照brute force的做法,将i回到b,将模式串头与b对齐开始比
by doing this you are already assuming the "abcdefg" have 前后重复子串
因为如果第二次匹配成功了,那么bcdefg与abcdef必然是重复的,也就是说abcdefg由重复前后缀
那么你的第二次尝试,实际上在假设失配位前面的子串的重复前后缀长度为n-1的前提下,将假设的前后缀对齐,
假如说第二次尝试还没成功,那么你还会继续将模式串往后挪一位,那么也就是你在假设abcdefg有重复前后缀abcde和cdefg,所以你其实是在一一列举从第一次失配以后,失配位前面的子串的重复前后缀长度,从n-1到0
根据这个逻辑,可以发明KMP
如果我们可以提前知道,对模式串的任意长度前缀,我们知道其的最长重复前后缀的长度,那么失配的时候直接对齐,不再一个一个挪,那岂不是能提高效率?
so this is j=next[j]
什么是next数组? next数组里存的是什么?
getnext操作是针对模式串(短串),next数组存的是当前位置下,最长重复前后缀的长度
getnext操作
getnext是一个找重复前后缀的过程,
next数组的生成过程,是一个不断扩大已知子串的过程,过程中指针 j 始终保持在已知子串的最大重复前缀的最后一个字的坐标上,然后每次扩展一个新字符,看这个新字符p[i]与p[j+1]是否相等,相等则j++,已知子串的最大重复前缀又大了一个,不相等则j=next[j]退回最大子串的最大子串的末尾字符下标,然后再p[i]与p[j+1]是否相等
如果一直不相等,则j退回0了,那么看p[i]与第一个字符p[1]是否相等,相等则j++,j=1,已知子串的最大重复前缀只有一个字符长,如果不相等,则j保持在0,表示已知子串没有重复前缀
01
aaa
12
KMP
getnext之后,我们回看刚才的案例:
i
abcdefghklyiuoiuh
abcdefgq
j
123456789
在h和q失配之后,我们通过查next数组即可得知,abcdefg没有重复前后缀,直接将h与a对齐即可
KMP失配后i指针原地不动,j指针回到最大重复子串的末尾,相当于将失配位前面子串的重复前后缀,模式串的前缀对齐到输入串的后缀,然后继续往下比,省了对重复前后缀的长度的逐一列举
性能提升:
暴力做法,时间复杂度O(n^2)
KMP: O(n)
AcWing yxc模板
模板要点:
1.数组下标从1开始,如果从0开始的话会不好弄
2.getNext过程中,j永远指向当前子串最长重复前后缀的最后一位
3.KMP过程中,j指向的是模式串已被识别的长度的最后一位
//getnext-----------------------------------------------------------------------------------------
//三步:
//1.回退
//2.判等是否加1,因为如果没有重复前后坠,那么j应该是0而不是1
//3.存储
void getnext()
{
for(int i=2,j=0;i<=n;i++)
{
while(j&&P[i]!=P[j+1]) j=ne[j];
if(P[i]==P[j+1]) j++;
ne[i]=j;
}
}
//KMP-------------------------------------------------------------------------------------------------
//三步:
//回退
//判等是否加一
//打印+回退
//补充:
//KMP失配后i指针原地不动,j指针回到最大重复子串的末尾
//getnext和KMP的前两步是重复的,只有第三步不一样
void KMP()
{
for(int i=1,j=0;i<=m;i++)
{
while(j&&S[i]!=P[j+1]) j=ne[j];
if(S[i]==P[j+1]) j++;
if(j==n)
{
printf("%d ",i-n);
j=ne[j];//如果要找到全部出现的位置,匹配成功后还要在回退一下
}
}
}