一、KMP算法的提出
我们上一篇博客说到,串的朴素模式匹配算法的效率极低,每次匹配不成功的时候主串和模式串都会进行回退。这样就会是算法的效率很低。于是就有一种KMP算法,大大得避免了重复遍历的情况,只让主串不回退,模式串回退。
2、KMP算法图解过程
我们设主串 S = “ABCABCABCCABCABCD”。模式串T = “ABCABCD”。KMP算法的匹配过程如下图所示:
对于kmp算法来说,由于上图中的第二次和第三次一定是匹配不成功的,所以第二步和第三步不执行。直接执行的是第四步。也就是这样:
所以我们现在需要求解的是失配点之前的已匹配部分是否存在最大前缀(T)和最大后缀相等(S)。只要求解出这个我们就可以省略中间较多不匹配的过程,直接到最大前缀和最大后缀相等的部分。这也是KMP算法的关键。KMP算法也就是为了让这些不必要的回溯发生。
由于失配点之前的元素是一一匹配的,就相当于S中的S[i]–S[i + k]与T中T[j]–T[j + k]的位置的元素都是一一对应互相相等的。我们求的是已匹配的部分也就是S中的后缀和T中的前缀最大的相等的部分可以转换成求T中的后缀和T中的前缀相等的部分。如公式:
T[j + k -x]–T[ j + k]
T[j]–T[j + x]
其中X为相同的个数。也就是每个失配点应该回退的位置。求这个X也我们接下来要说的next数组
三、next数组
我们继续用上面的例子模式串T=“ABCABCD”
它的next数组就是[-1,0,0,0,1,2,3]具体过程如下图所示:
就如上图所示,如果在最后一个位置D的位置匹配失败,我们发现它的next数组是3,所以他只要回退到3号位置的下标就可以了。它可以省略好多中间不必要的回退,大大提高算法的效率。这一句是KMP算法的核心。
四、代码实现KMP算法
实现KMP算法之前我们必须得到next数组
void getNext (char* t,int next[])
{
int len = strlen(t) ;
int j=1;
int k=0;
if (len == 0)
{
return;
}
next[0] = -l;
if (len > 1)
{
next[1] = 0;
}
while(j〈len-1)
{
if (k == -1 || t[k] == t[j])//所有的匹配都已完成,没有任何最大的前缀和最大后缀相等
{
next[j+1]=k+1;
j++;
k++ ;
}
else
{
k = next[k];
}
}
}
void showNext(int next[] int len)
{
for(int i = 0;i < len; i++)
{
printf("%d",next[i]);
}
printf("\n");
}
int KMP (char* s, char* t)
{
int slen = strlen(s);/
int tlen = strlen(t);//模式串的长度
int i = 0;//i 主串的下示
int j = 0;//j 模式串的下标
int* next = (int*)malloc(sizeof(int)*tlen);
getNext(t,next);
showNext(next,tlen);
while (i < slen && j < tlen)
{
if (j == -1 || s[i] == t[j])
{
i++;
j++;
}
else
{
j = next[j];
}
}
if(j == tlen)
{
return i - tlen;
}
return -1;
}
KMP算法的优化与推进,也就是求nextval数组的值详见博客https://blog.csdn.net/qq_43411563/article/details/105863737