KMP算法要解决的问题就是在字符串(也叫主串)中的模式(pattern)定位问题。说简单点就是我们平时常说的关键字搜索。模式串就是关键字(接下来称它为P),如果它在一个主串(接下来称为T)中出现,就返回它的具体位置,否则返回-1(常用手段)。
首先,对于这个问题有一个很单纯的想法:从左到右一个个匹配,如果这个过程中有某个字符不匹配,就跳回去,将模式串向右移动一位。这有什么难的?
我们可以这样初始化
之后我们只需要比较i指针指向的字符和j指针指向的字符是否一致。如果一致就都向后移动,如果不一致,如下图:
#include <stdio.h>
#include <string.h>
int BF(const char *src, const char *des, int len1, int len2)
{
if(src == NULL || des == NULL) return -1;
if(len1 < len2) return -1;
int i = 0;
int j = 0;
while(i < len1 && j < len2) {
if(src[i] == des[j]) {
i++;
j++;
} else {
i = i - j + 1;
j = 0;
}
}
if(j == len2) {
return i - j + 1;
}
return -1;
}
int main()
{
char src[] = "abfdushffdfdkl";
char des[] = "fdush";
int len1 = strlen(src);
int len2 = strlen(des);
int k = BF(src, des, len1, len2);
printf("%d\n", k);
return 0;
}
如果是人为来寻找的话,肯定不会再把i移动回第1位,因为主串匹配失败的位置前面除了第一个A之外再也没有A了,我们为什么能知道主串前面只有一个A?因为我们已经知道前面三个字符都是匹配的!(这很重要)。移动过去肯定也是不匹配的!有一个想法,i可以不动,我们只需要移动j即可,如下图:
上面的这种情况还是比较理想的情况,我们最多也就多比较了再次。但假如是在主串“iiiiiiiiiiiiiiiiiiiia”中查找“iiiiib”,比较到最后一个才知道不匹配,然后i回溯,这个的效率是显然是最低的。
KMP算法其思想就如同我们上边所看到的一样:“利用已经部分匹配这个有效信息,保持i指针不回溯,通过修改j指针,让模式串尽量地移动到有效的位置。”
所以,整个KMP的重点就在于当某一个字符与主串不匹配时,我们应该知道j指针要移动到哪?
接下来我们自己来发现j的移动规律:
如图:C和D不匹配了,我们要把j移动到哪?显然是第1位。为什么?因为前面有一个A相同啊:
如下图也是一样的情况:
可以把j指针移动到第2位,因为前面有两个字母是一样的:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
void GetNext(const char *des, int *next, int len)
{
if(des == NULL || next == NULL) return;
int i = 0;
int k = -1;
next[0] = -1;
while(i < len - 1) {
if(k == -1 || des[k] == des[i]) {
if(des[++k] == des[++i]) {//小优化
next[i] = next[k];
}
} else {
next[k] = k;
}
}
}
int KMP(const char *src, const char *des)
{
if(src == NULL || des == NULL) return NULL;
int len1 = strlen(src);
int len2 = strlen(des);
if(len2 > len1) return NULL;
int *next = (int *)malloc(sizeof(int) * len2);
GetNext(des, next, len2);
int i = 0;
int j = 0;
while(i < len1 && j < len2) {
if(src[i] == des[j] || j == -1) {
i++;
j++;
} else {
j = next[j];
}
}
free(next);
if(j == len2) {
return i - j;
}
return -1;
}
int main()
{
return 0;
}