KMP算法的代码看起来很简单,但是想要理解其原理却非常的困难。在这里建议初学者多多找讲解KMP算法的视频来观看,个人认为视频更容易帮助人来理解,只通过文字的讲解看起来是十分困难的,还有个人也要多多的思考。当理解了原理过后,具体的代码写起来就很简单了,它与BF算法十分相似,只是多了个求next数组的步骤。
KMP算法由匹配部分与求next数组部分,两部分组成。 匹配时的算法特点: (1)需要有一个指向主串的指针i与指向模式串的指针j,且两者都应该初始化为0; (2)i永不回退,只回退j,且j的回退步数由next数组决定,即j=next[j]; (3)循环条件为i<s_length&&j<t_length。
KMP算法:
s是主串,t是子串,next表示next数组
int KMP(char s[],char t[],int next[])
{
int i=0;
int j=0;
int s_length=strlen(s); //求字符串s的长度
int t_length=strlen(t); //求字符串t的长度
while(i<s_length&&j<t_length)
{
if(j==-1||s[i]==t[j]) //当j等于-1的时候退化到BF算法
{
i++;
j++;
}
else
{
j=next[j]; // i永不回退,只回退j
}
}
if(j==t_length)
{
return i-j;
}
else
{
return -1;
}
}
next数组的特点: (1)求next数组时需要一个前缀指针j以及后缀指针i,且前缀指针j应初始化为-1,后缀指针应初始化为0; (2)next数组的首元素为-1,即next[0]=-1; (3)因为要求每个子串的最长相等前后缀,故循环条件为i<length。 下面是求next数组的过程:
//str指向模式串,next用来保存next数组
void GetNext(char* str, int* next)
{
int j = -1; //j是模式串的前缀
int i = 0; //i是模式串的后缀
next[0] = -1;
int length=strlen(str);
while (i < length)
{
if (j == -1 || str[i] == str[j])
{
i++;
j++;
//注意记录next数组里面的值哦
next[i] = j; //如果第i个字符与第j个字符相同,那么next[i]的值就应该等于前缀j的值
}
else
{
j = next[j]; //否则的话,j跳到拥有最长相等前后缀的那个位置,再次比较
//其实这个过程就相当于求模式串的子串的最长相等前后缀
}
}
}
通过以上的观察可以看到,求next数组函数里的循环体部分与KMP算法的匹配部分的循环体部分几乎是一样的,除了循环条件。为什么会这样呢?其实求next数组的过程就相等于,模式串是主串,而模式串的子串又相当于模式串,然后再求两者的匹配过程。详细地说,就是子串为str[0], str[0]str[1],str[0]str[1]str[2]…………时,每个子串都与主串匹配了一遍。
以下详细说一下:当str[i]!=str[j]时,为什么j要回退到next[j]这个位置?
当str[i]!=str[j]时,应该就先
(1)比较str[0]与str[i],即j=0,比较str[j]与str[i],设k=0;
(2)如果相等,则继续比较,str[0]与str[i-1],即j++
如果相等,又继续比较,str[1]与str[i],即j++
(3) ……
继续比较,直到s[j]!=s[i]时,那么就找到了最长相当前后缀。
但实际上,上述需要比较多少次,我们是可以直接通过next数组知道的。即比较次数可以由next[j]来快速确定。