KMP算法(Knuth-Morris-Pratt算法)是一种用于字符串匹配的经典算法,其目的是在一个主文本串(text)中高效地查找一个模式串(pattern)的出现位置。相较于朴素的字符串匹配算法,KMP算法具有更高的效率,特别是在处理大文本和长模式串时。
引出KMP算法之前,我们必须了解朴素的字符串匹配算法(暴力算法)的实现
假设主文本串 text = “ababcabcababcab”,模式串 pattern = “ababcab”
朴素算法(暴力算法的实现)
假设文本串为"ababcaccababcab",模式串为"ababcab";
朴素算法的实现过程:
两个指针分别指向两个字符串的首字母,逐个匹配。
像这样,如果我们遇到不匹配的情况我们要进行回溯,重新进行匹配。
文本串回溯到到开始匹配位置的下一个位置,模式串回溯到起始位置,重新匹配
按照上面的过程以此类推,最后成功匹配字符串。那么判断成功匹配的标志是什么呢?
判断成功的标志就是:指向文本串的指针刚好指向末尾或者还没指向末尾,但是模式串指针已经指向末尾了,就算匹配成功。
int substr(char* arr1.char* arr2) {
int i = 0;//指向文本串
int j = 0;//指向模式串
for (int i = 0; i < strlen(arr1); i++) {
//for循环记录当前从哪个位置开始匹配
int cur = i;//cur记录文本串当前匹配的位置
while (cur < strlen(arr1) && j < strlen(arr2) && arr1[cur] == arr2[j]) {
//只要cur和j都在各自字符串范围内,并且字符匹配,指针就指向下一个字母
cur++; j++;
}
//循环中断有三种情况,分别处理:
//1.cur指向的字符串结束,j指向的字符串还未找到子串,说明找不到子串,返回-1.
if (cur == strlen(arr1) && j < strlen(arr2)) return -1;
//2.j指向的字符串到结尾,说明模式串的所有字符匹配成功 返回文本串开始匹配的位置
if (j == strlen(arr2)) return i;
//3.由于字符不匹配导致的循环中断,模式串指针回到起点
j = 0;
}
}
}
朴素算法的弊端:
字符串如果匹配失败,就会在文本串开始匹配的位置的下一个字母再匹配,如果是非常长的字符串,这样的时间复杂度是非常高的。能不能在这匹配的基础上加点优化呢?
KMP算法的原理
假设是这样一个字符串,我们希望找到一些有效信息来避免一些重复的比较。
我们不难发现文本串的5号a和模式串的5号a匹配,然后0号也是a。
那么他们三者相等,我们可不可以直接从文本串的第6位开始匹配,模式串的第2位开始匹配呢?
像这样,就会省略了很多重复的过程,加大了匹配效率。
那我们就希望,在匹配不成功的时候,找到匹配不成功前一个位置的串中相同的部分,
比如:ababcab 最后字母b的前字符串:ababca 中相同的部分第一个a 和 最后一个 a
由于,两个a都已经和文本串匹配,内部之间的a又已经匹配,那么下一次匹配的时候第一个a就不用匹配了,直接匹配第一个a的下一个字母,省略重复的步骤。
那么怎么获取字符串相同的部分呢,我们用一个数组存储这个相同的部分,称为next数组
next数组的获取
next数组其实就是一个字符串内部的匹配过程
next[i]表示第i个字母前相同的部分最大的字符数
第一个字母只有a,最起码有两部分才能有相同的部分,所以next[1]=0;
第一个字母和第二个字母进行匹配,无法匹配,next[2]=0;
第一个字母和第三个字母比较,匹配成功,next[3]=1;,指针加一。
第二个字母和第四个字母比较,匹配成功,next[4]=2; 相同部分又2个字符
第三个字母和第五个字母比较,匹配失败,这时候和上面机制是一样的,先找第三个字母前有相同的部分,回到前面那个字母的下标,比如aba的第一个a无法匹配,那么这个位置的相同部分为0,next[5]=0;
匹配成功,next[6]=1;
匹配成功,next[6]=2;
这样next数组就找到了
我们来看看代码
for(int j=0,i=2;i<=n;i++){
//j=0,方便找到j前的字符串相同的部分
while(j && p[j+1]!=p[i]) j=ne[j];
if(p[j+1]==p[i]) j++;
ne[i]=j;//i位置相同的部分就是j长度
}
如果了解了next数组怎么来,主函数部分就简单了
//和next数组的匹配差不多
for(int j=0,i=1;i<=m;i++){
while(j && p[j+1]!=s[i]) j=ne[j];
//如果不匹配,让j回到第一个相同的部分的下标
if(s[i]==p[j+1]) j++;
if(j==n){
cout << i-j << " ";
}
}
以上是KMP我对KMP算法的理解,但是我觉得理解的不是很好,如果有错误请指出,感谢!创作不易,如果您们有收获的话,可以点下赞吗,谢谢啦!