一、认识KMP算法
KMP算法是一种改进的字符串匹配算法,核心是利用匹配失败后通过一个next数组,使得减少匹配串和主串的匹配次数达到提高效率。
KMP算法和KF算法(暴力求解的算法)不同的是,主串的 i 不会回退,而匹配串的 j 会有规律的回退到不同位置。
二、KMP算法实现思路
让我们来一个例子
我们将下面这串字符当作主串
这串字符当作匹配串
1、为什么不用回退 i
我们给它们标上坐标号
通过下面我们可以知道, i 和 j 在5号位置匹配失败,这个时候如果回退 i 到1号位到b,还是不匹配成功的,所以我们可以尝试用一些方法简化一下。
我们观察到,在匹配失败的位置前,都是匹配成功了的,再观察到,在前面的ab出现了两组,那么 j 如果在5号位匹配失败,就可以在2号位再试试匹配,如果不成功那么就从0号位开始。
那么如何有规律的确定 j 回退的位置呢?
2、求next数组
KMP中的next数组:next[ j ] = k,在next数组里每个 j 位置对应着一个k值,这个k值就是 j 需要移动的坐标值。
k的规则:
- 找到匹配成功的部分子串,一个从0坐标开始,一个在j-1坐标结尾。
- 不管是什么字符串,next[0]=-1,next[1]=0。
比如:
next[5]=2,假设字符串数组名是p,j=5,从坐标0开始p[0]->p[1]==p[3]->p[4],ab长度是2,所以k=2。
next[4]=1,j=4,从坐标0开始p[0]==p[3] (j-1),a长度是1,所以k=1。
所以数组p的next数组为
下面有两个关于next数组的练习题:
答案:
3、确定 j 回退的位置
前面我们通过人为推断确定了next各个位置的值,但是在程序中我们如果已知0和1号位置的k值(-1和0),那么该如何确定其它位置的值。
我们先通过人为推断的结果找找规律。
我们知道了next数组中的 k 是怎么来的。
接下来让我们看看其中的推断。
用这个字符串数组举例
4、总结思路
- 通过p[ j ]是否等于p[ k ],确定next数组各个位置的k值。
- 再进行正常匹配, 如果匹配不成功,就将 j 移动到next数组对应到的位置。
- 最后通过返回主串 i 的位置和匹配串 j 的位置差,确定字符串从哪个位置开始的匹配。
三、KMP算法实现代码
//通过sub[i]与sub[k]关系确定next中各个的值
void GetNext(char* sub, int* next, int len)
{
next[0] = -1;
next[1] = 0;
int i = 1;//从已知的1位置开始
int k = 0;//1位置已知的k值
while (i < len)
{
if (k == -1 || sub[i] == sub[k])
{
next[i + 1] = k + 1; //推论
k++;
i++;
}
else
{
k = next[k]; //不一样就回退
}
}
}
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 >= lenstr)
{
return -1;
}
int i = pos;
int j = 0;
//开辟next数组
int* next = (int*)malloc(lensub * sizeof(int));
assert(next);
//获得next数组
GetNext(sub,next,lensub);
//开始匹配
while (i < lenstr && j < lensub)
{
if (j == -1 || str[i] == sub[j]) //j在一开始匹配不成功就会为-1
{
i++;
j++;
}
else
{
j = next[j]; //匹配不成功j退回next[j]位置
}
}
if (j >= lensub)
{
return i - j;
}
else
{
return -1;
}
free(next);
next = NULL;
}
int main()
{
printf("%d\n", KMP("bbcabcsabdbb", "abcsa", 0)); //3
printf("%d\n", KMP("abbbbc", "bbc", 0)); //3
return 0;
}
四、总结
本章思路是:介绍KMP算法,理解next数组以及k怎么来的,通过next数组中的结论看到推论,再通过推论反推next数组。
KMP算法主要是在 i 和 j 位置匹配不成功情况下,通过next数组改变j的位置进行重新匹配,然后就是如何确定next数组。