KMP算法,由
,
和
三位大神共同提出,是一种改进的字符串匹配算法。
对于如“求长度为
的字符串
中在包含多少个长度为
的模式串
”这类问题,相对于从前往后逐字符比较的时间复杂度为
纯暴力算法Brute-Force,优化后的KMP算法可以在
的时间复杂度内实现两个字符串的匹配。
很显然,当我们考虑Brute-Force算法的过程时,我们会发现当在主串中逐字符匹配模式串时,主串中互相重叠的模式串会重复匹配到重叠的部分。
而同样显而易见,重叠的部分对于前面的模式串来说是前面模式串的后缀,对于后面的模式串来说是后面模式串的前缀,所以说“跳过重叠部分”的问题就可以被转换成“预处理字符串前后缀的情况”的问题了。
KMP算法的核心在于一个被称为“失配指针”或者“部分匹配表”的数组
。
对于给定下标从
开始字符串
,此时
的意义为
前
个元素的最大的
,使得
前
个元素
构成的子串的
前
个元素构成
的字符串与
后
个元素构成
的字符串
相同,且为了方便实现,我们指定
。
如字符串
对应的
即为:
遍历字符串
求
数组时,假设当前遍历到的元素为
,上一位元素
对应的
为
。
如果
,由于对于
个元素
构成的子串
,有
的
前
个元素和
后
个元素构成的字符串
相同。所以
的
前
个元素构成的字符串在
结尾加上
后,依然与
的
后
个元素构成的字符串在
结尾加上
后相同。
所以此时
,成立条件为
,这是第一种情况。
如果
,那么显然对于
在
结尾加上
后,对应的
必然小于
。
又因为对于
有
的
前
个元素和
后
个元素构成的字符串
相同,而
数组记录的又是字符串的前缀后缀相等的
最大值,所以对于
与
失配的情况,我们要让
取到最大值,我们就要保留
尽可能多的相等的前后缀字符串。
而
中
前
个元素和
后
个元素构成的字符串是
中
最大的使得前后缀字符串相等的字符串长度,所以此时继续对于
和
匹配,若能够满足
那当前就是
第一种情况,若不能满足那么当前就是不能匹配的第二种情况,也就是
重复当前步骤直到出现第一种情况或者当前的
数组匹配到
。
所以此时对于
,不断重复
直到
或
时
。
求
部分核心代码如下:
Next[0]=-1;
int j=0,k=-1,len=strlen(str);
while(j<len)
if(k==-1||str[j]==str[k])
Next[++j]=++k;//赋值
else
k=Next[k];//跳next
显然,我们可以发现在每一次赋值时,如果对应的
,那么在下一次匹配中必然会跳
,所以我们可以直接将当前
赋值为
。
求
部分核心代码如下:
Next[0]=-1;
int j=0,k=-1,len=strlen(str);
while(j<len)
if(k==-1||str[j]==str[k]){
++j,++k;
if(str[j]!=str[k])
Next[j]=Next[k];
else
Next[j]=k;//赋值
}
else
k=Next[k];//跳next
这时我们再从
数组回到整个KMP算法,当我们要将主串
和模式串
进行匹配时,对于我们已经求好的
的
数组,我们可以以同样的方法进行匹配。
也就是说匹配
的前缀字符串的后缀字符串和
的前缀字符串,这样我们也就可以在失配时保留
尽可能多的已经匹配上的部分了。
匹配答案部分核心代码如下:
//匹配主串str1中包含多少模式串str2
i=0,k=0;//注意模式串此时同样也需要从0开始而非-1
len1=strlen(str1);
len2=strlen(str2);
while(i<len1){
if(k==-1||str1[i]==str2[k])
++i,++k;
else
k=Next[k];
if(k==len2){//若主串中包含模式串
++ans;
k=Next[k];
}
}
例题:
【模板】KMP字符串匹配 - 洛谷www.luogu.com.cn https://loj.ac/p/103loj.ac