KMP是什么?
Knuth–Morris–Pratt(KMP)算法是一种改进的字符串匹配算法,它的核心是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。它的时间复杂度是O(m+n)。具体实现就是通过一个next()函数实现,函数本身包含了模式串的局部匹配信息。
什么时候会用到KMP算法?
字符串匹配问题,例如:实现strstr
KMP算法的主要思路
在之前遇到字符串匹配问题时,当模式串与主串匹配失败时,暴力求解法就是返回模式串的第一位和主串的下一位进行比较,而KMP利用了一个next数组减少了主串的回溯
举个例子:主串为“ababababca“,模式串为“abababca”
在暴力算法中当匹配到模式串中的‘c’时,需要返回模式串的第一位‘a’和主串的第二位‘b’,重新开始匹配
在KMP中,当遇到模式串的‘c’和主串中第四个‘a’不匹配时,会通过next数组返回上一个匹配成功的位置再接着与主串进行匹配,这个next数组中存入的就是字符串的前缀集合与后缀集合的交集中最长元素的长度。
- 什么是字符串的前缀和后缀?
如果字符串A和B,存在A=BS,其中S是任意的非空字符串,那就称B为A的前缀;同样可以定义后缀A=SB, 其中S是任意的非空字符串,那就称B为A的后缀。
举个例子:“hello”的前缀有:{‘h’,‘he’,‘hel’,‘hell’};后缀有:{‘ello’,‘llo’,‘lo’,‘o’}
构建next数组
void getNext (int *next, char *str02) {
int len02 = (int)strlen(str02);
next[0] = 0; //设置next数组首元素为0
int i, m;
for (i = 1, m = 0; i < len02; i++) { //i为next数组下标,m为模式串下标
next[i] = m; //上一个循环计算过的m
while (m > 0 && str02[i] != str02[m]) { // 当m不在首位置并且模式串中字符失配时将m回溯到上一次匹配成功的地方进行判断
m = next[m];
}
if (str02[i] == str02[m]) {//字符匹配时m加1
m++;
}
}
return;
}
strstr函数的实现
void getNext (int *next, char *str02) {
int len02 = (int)strlen(str02);
next[0] = 0;
int i, m;
for (i = 1, m = 0; i < len02; i++) {
next[i] = m;
while (m > 0 && str02[i] != str02[m]) {
m = next[m];
}
if (str02[i] == str02[m]) {
m++;
}
}
return;
}
int kmp (char *str01, char *str02) {
int next[100];
getNext(next, str02);
int i, j;
int len01 = (int)strlen(str01);
int len02 = (int)strlen(str02);
for (i = 0, j = 0; i < len01; i++) { // 从0开始判断
while (j > 0 && str02[j] != str01[i]) {
j = next[j];
}
if (str02[j] == str01[i]) { //字符匹配成功,i,j下标同时后移
j++;
}
if(j == len02){ //模式串匹配完成
return (i - j + 1);
}
}
return -1;
}