KMP简介
KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt提出的,因此人们称它为克努特—莫里斯—普拉特操作(简称KMP算法)。KMP算法的核心是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。
KMP与BF(暴力算法)的不同
BF算法
当i与j不相等,i回到起始位置的下一个,j回到起始位置。
KMP算法
当i与j不相等,i不回退,j回退----回退到哪里?下面会讲到。
BF与KMP不同
BF算法,当主串与格式串不匹配,i与j都要回退,且j回退到起始位置,i回退到起始位置的下一个位置
KMP算法,当主串与格式串不匹配,j回退到特定位置,i不用回退。
这样看起来,KMP算法优于BF算法,时间更快。
KMP算法详细讲解
看上图,我们知道KMP算法其实,就是查看主串a中是否含有子串b的字符串。
KMP算法就是匹配失败后,主串中的i不用回退
只需要回退子串中的j到子串中的某个位置
所有KMP算法与主串无关,我们分析子串就够了。
那眼见看上面的图片,第一次匹配失败后,j回退到了子串中的第三个元素,前面两个元素不用再去比较了。
这也是KMP算法最厉害的地方。
这个是子串,为什么当第六个元素匹配失败后,就回到第三个元素再比较呢。
因为,第一个第二个元素与第四个第五个元素相等----->黄色的内容与绿色的内容相等。
这里我找的是一个特别的例子,为了便于你们理解。
所以我们应该让黄色后面的数据,与绿色后面的数据比较。
就比如让我们自己用眼睛找,我们先从第一个a开始依次往后找,如果错了,就再找下一个a继续对照,原理差不多。
在这只要理解,为什么这样移动就好,先不用管是如何移动的。
最长公共前后缀
首先,我们要了解字符串中的前后缀是什么?
请看图片😄。
前缀是从前面读不读最后一个字符
后缀是从第二个元素读不读第一个字符。
最长公共前后缀,就是前缀与后缀内容相同且长度最长==(但是不能等于字符产本身)==。
比如:上图的最长公共前后缀是6。
知道最长公共前后缀后,我们来了解一下,当匹配失败后,子串如何回退的。它是通过一个next数组进行回退的。
next数组
next数组记录的是子串匹配失败后,j应该回退到哪里。
数组中的数与最长公共前后缀有关。
肯定有人会问,如何写到程序中呢,这些都是用眼睛看到的呀
第一种情况
第二种情况
看完上面还会有点不理解,直接看代码
代码实现
#include<stdio.h>
#include<stdio.h>
#include<assert.h>
#include<string.h>
#include<stdlib.h>
void Getnext(char* sub, int* next,int Lensub)//求next数组
{
next[0] = -1;
next[1] = 0;
int i = 2;//当前i下标
int k = 0;//前一项的K
while(i<Lensub)
{
if (k==-1||sub[i - 1] == sub[k])
{
next[i] = k + 1;
k++;
i++;
}
else
{
k = next[k];
}
}
}
//str 主串
//sub 子串
//pos 从主串的pos位置开始找
int KMP(char* str, char* sub, int pos)
{
assert(str && sub);
int Lenstr = strlen(str);
int Lensub = strlen(sub);
if (Lenstr == 0 || Lensub == 0)
return -1;
if (pos < 0 || pos >= Lenstr)
return -1;
int* next = (int*)malloc(sizeof(int) * Lensub);
assert(next);
Getnext(sub, next, Lensub);
int i = pos;//遍历主串
int j = 0;//遍历子串
while (i < Lenstr && j < Lensub)
{
if (j == -1||str[i] == sub[j])
{
i++;
j++;
}
else
{
j = next[j];
}
}
free(next);
next = NULL;
if (j >= Lensub)
{
return i - j;
}
return -1;
}
int main()
{
char str[] = "shjkdhsad";
char sub[] = "kdh";
int k = KMP(str, sub,0);
printf("%s\n", str + k);
}
nextval数组
nextval数组是next数组的升级
为什么要升级呢?-----肯定有缺陷
话不多话说看代码吧
void Getnextval(char* sub, int* nextval, int Lensub)
void Getnextval(char* sub, int* nextval, int Lensub)
{
nextval[0] = -1;
int i = 1;//当前i下标
int k = -1;//前一项的K
while (i < Lensub)
{
if (k == -1 || sub[i-1] == sub[k])
{
k++;
if (sub[i] != sub[k])
{
nextval[i] = k;
}
else
{
nextval[i] = nextval[k];
}
i++;
}
else
{
k = nextval[k];
}
}
}
有写的不好的地方希望大家指出来😄