本文参考了一位大佬的文章:把kmp算法解析的非常清楚,大家可以看看。
假设现在我们面临这样一个问题:有一个文本串和模式串,要在文本串中查找模式串的位置,有一种暴力匹配的模式,假设文本串匹配到i位置,模式串匹配到j位置,则有:
如果当前字符匹配成功,这i++,j++,继续匹配下一字符;
如果失败,令i=i-(j-1),j=0。相当于每次匹配失败,i回溯,j被置为。
理解了暴力模式匹配算法的思想,暴力匹配思想的代码如下;
#include
using namespace std;
int fun(char *s, char *p)
{
int slen = strlen(s);
int plen = strlen(p);
int i = 0, j = 0;
while (i < slen && j < plen)
{
if (s[i] == p[j])
{
i++;
j++;
}
else
{
i = i - j + 1;
j = 0;
}
}
if (j == plen)
{
return i - j;
}
return -1;
}
int main()
{
char s[20], p[20];
scanf("%s %s", s, p);
int n = fun(s, p);
cout << n << endl;
return 0;
}
下面先给出KMP的算法流程
假设现在文本串s匹配到i位置,模式串匹配到pj位置
如果j=-1,或者当前字符匹配成功(s[i]==p[j]),都令i++,j++,继续匹配下一个字符;
如果j!=-1,且当前字符匹配失败,则令i不变,j=next/[j]。此举意味着匹配失败时,模式串相对于文本串向右移动了j-next[j]位。
换言之,当匹配失败时,模式串向右移动的位数为:失配字符所在的位置-失配字符对应的next值,即实际的移动位数为:j-next[j],且此值大于1。
kmp的实现代码如下;
int KmpSearch(char *s, char *p, int *next)
{
int i = 0;
int j = 0;
int sLen = strlen(s);
int pLen = strlen(p);
while (i < sLen && j < pLen)
{
//①如果j = -1,或者当前字符匹配成功(即S[i] == P[j]),都令i++,j++
if (j == -1 || s[i] == p[j])
{
i++;
j++;
}
else
{
//②如果j != -1,且当前字符匹配失败(即S[i] != P[j]),则令 i 不变,j = next[j]
// next[j]即为j所对应的next值
j = next[j];
}
}
if (j == pLen)
return i - j;
else
return -1;
}
求next数组的代码如下:
void GetNext(char *p, int *next)
{
int pLen = strlen(p);
next[0] = -1;
int k = -1;
int j = 0;
while (j < pLen - 1)
{
// p[k]表示前缀,p[j]表示后缀
if (k == -1 || p[j] == p[k])
{
++k;
++j;
next[j] = k;
}
else
{
k = next[k];
}
}
}
下面是完整代码的第一种写法,此种写法没有把求next数组的过程写成函数。
#include
using namespace std;
int KmpSearch(char *s, char *p, int next[20])
{
int i = 0;
int j = 0;
int sLen = strlen(s);
int pLen = strlen(p);
while (i < sLen && j < pLen)
{
//①如果j = -1,或者当前字符匹配成功(即S[i] == P[j]),都令i++,j++
if (j == -1 || s[i] == p[j])
{
i++;
j++;
}
else
{
//②如果j != -1,且当前字符匹配失败(即S[i] != P[j]),则令 i 不变,j = next[j]
// next[j]即为j所对应的next值
j = next[j];
}
}
if (j == pLen)
return i - j;
else
return -1;
}
int main()
{
char s[20], t[20];
scanf("%s %s", s, t);
int plen = strlen(t);
int next[20];
next[0] = -1;
int k = -1;
int j = 0;
while (j < plen - 1)
{
if (k == -1 || t[j] == t[k])
{
++k;
++j;
next[j] = k;
}
else
{
k = next[k];
}
}
cout << KmpSearch(s, t, next) << endl;
return 0;
}
下面第二种写法是把求next数组的部分也写成了一个函数,推荐使用这种写法
#include
using namespace std;
void GetNext(char *p, int *next)
{
int pLen = strlen(p);
next[0] = -1;
int k = -1;
int j = 0;
while (j < pLen - 1)
{
// p[k]表示前缀,p[j]表示后缀
if (k == -1 || p[j] == p[k])
{
++k;
++j;
next[j] = k;
}
else
{
k = next[k];
}
}
}
int KmpSearch(char *s, char *p, int *next)
{
int i = 0;
int j = 0;
int sLen = strlen(s);
int pLen = strlen(p);
while (i < sLen && j < pLen)
{
//①如果j = -1,或者当前字符匹配成功(即S[i] == P[j]),都令i++,j++
if (j == -1 || s[i] == p[j])
{
i++;
j++;
}
else
{
//②如果j != -1,且当前字符匹配失败(即S[i] != P[j]),则令 i 不变,j = next[j]
// next[j]即为j所对应的next值
j = next[j];
}
}
if (j == pLen)
return i - j;
else
return -1;
}
int main()
{
char s[20], t[20];
scanf("%s %s", s, t);
int plen = strlen(t);
int *p;
p = (int *)malloc((plen + 1) * sizeof(int));
GetNext(t, p);
cout << KmpSearch(s, t, p) << endl;
return 0;
}
求next数组的值得过程可以进行一定的优化,总结起来就是,他是在计算出next值的同时,如果a位字符与他next值指向的b位字符相等,则该a位的nextval就指向b位的nextval值,如果不等,则该a位的nextval值就是他自己原来的next值。
求next数组的优化代码如下,又称为求nextval数组:
void GextNextval(char *p, int *next)
{
int plen = strlen(p);
int k = -1;
int j = 0;
next[0] = -1;
while (j < plen - 1)
{
if (k == -1 || p[k] == p[j])
{
k++;
j++;
if (p[k] != p[j])
next[j] = k;
else
next[j] = next[k];
}
else
{
k = next[k];
}
}
}
kmp算法总体来说较难理解,同学们还得自己多多研究。