这个算法的目标是实现字符串查找功能,解决了字符串查找中匹配失败后回溯重新匹配的问题。
目标:在 abbaabbaaba字符串中找到 abbaaba 。
甲(data):abbaabbaaba
乙(pattern):abbaaba
传统的暴力求解:甲从头开始与乙一一比较,发现第 7 个字符不匹配。甲会回退到自己的第 2 个字符,乙则回退到自己的开头,然后两人开始重新比较。然后 不匹配,回退,不匹配,回退,……
KMP的核心思想:甲不回退,只进行乙回退。那么,乙需要回退多少才能保证甲即使不回退,甲之前位置的字符与乙就匹配呢?这时候就需要引入 最大公共元素单元长度 这个概念了。下面的表格简述了最大公共元素单元长度的计算方式。
pattern最大公共元素单元:
长字符串的最大公共元素是在短子字符串的最大公共元素基础上增加的,如 A, AB, ABC, ABCA ....
这个结论很重要,后面乙回退的位置都是子字符串的最大公共元素所处于的偏移位置。
所有最大公共元素单元长度组成的数据就是我们需要的next数组,那怎么求呢?
next数组中第n个位置存储的是长度为n的字符串中的最大公共元素单元长度。
为什么k缩小公式为 k=next[k-1] ?
其实本质上就是找k公共子串以外的其他公共子串,最长的不行,挑短的试试。
之所以是next[k-1],是因为 next[k-1]的最长公共子串,同样也是pattern[k]的公共子串,只是不是最长的而已。(根据对称性很容易得到)而pattern[k-1] pattern[k-2]这些并不是pattern[k]的公共子串。
构造next数组
void make_next(char *pattern, int *next)
{
int q, k;
int m = strlen(pattern);
next[0] = 0;
for(q = 1, k = 0; q < m; q++)
{
while (k > 0 && pattern[q] != pattern[k])
k = next[k-1];
if (pattern[q] == pattern[k])
{
k++;
}
next[q] = k;
}
}
KMP算法
KMP的过程非常接近next的求取过程,不过会将next提前算好。data数据每次移后一个位置,然后遍历next去进行匹配,不过这儿的next遍历的步长不是1,也不是固定的 ,而是当前pattern[j]字符串最大公共子串位置,也就是 j = next[j-1]。
为什么这么选,上面也有说了,next[j-1]位置的公共子串也是pattern[j]的公共子串,只不过长度短了些。而pattern[j-1] pattern[j-2]这些并不是pattern[j]的公共子串。
int kmp(char *data, char *pattern)
{
int i, j;
int n = strlen(data);
int m = strlen(pattern);
if (n < m)
{
return -1;
}
int next[100] = {0};
make_next(pattern, next);
for (i = 0, j = 0; i < n; i++)
{
// 此步骤后,如果没有匹配到,j的值为0,所以下次i循环时,j会从0开始
// 每一次i循环,都会按照next子串匹配一遍,成功 j==m,跳出循环;
// 否则继续下次循环,再遍历next子串。
// 之所以是next[j-1],是因为 next[j-1]的最长公共子串,
// 同样也是pattern[j]字符串的公共子串,只是不是最长的而已。(根据对称性很容易得到)
while (j > 0 && data[i] != pattern[j])
j = next[j - 1];
if (data[i] == pattern[j])
{
j++;
}
if (j == m)
break;
}
if (j == m)
return i-m+1;
else
return -1;
}