KMP算法的特点:相较于BF算法,在KMP算法中 当主串位置对应的字符与子串位置对应的字符不相同时,主串的查找位置不会进行回退,而子串查找的位置会回退到一个特定的位置上。
这个特定的位置该如何求呢?或者说是什么呢?
对于这个特定位置,KMP算法规定 子串中每个位置都有一个要回退的特定位置,并将这些特定位置单独存放到一个数组中,我们称这个数组为next数组
这些特定位置的求法: KMP算法规定:子串中第一个元素和第二个元素的对应的next数组的值(即第一个元素如果没有匹配成功,回退的位置;第二个元素当没有匹配成功,要回退的位置)分别为 -1 ,,,0 【此处在其他情况下,也被认为分别是0,,,1; 这些不会影响后续的运算逻辑,后续对应的next数组存放的元素都比 -1 ,,,0那种要 大1】 { 下文中我们是带入的子串前两个元素对应的next数组的值分别为 -1 ,,,0的情况 }
求法:记子串为sub 数组; KMP算法规定: 在sub[ j ] 之前寻找两个以sub[ 0 ] 的字符为开头,以 sub[ j - 1 ]的元素为结尾的两个完全相同的字符串【这两个字符串是有规定的,第一个字符串必须从sub[ 0 ] 开始。 第二个字符串必须在sub[ j - 1 ] 结束; 】,这个字符串的长度就是next[ i ] 的值。 如果找不到两个以sub[ j - 1 ] 开头,以sub[ j - 1 ] 结尾的两个数组,则记该next[ i ] = 0;
eg:
当 j 在下标为3的位置处; sub[ 0 ] = ' a ' ;sub[ j - 1 ] = ' c '; 因此我们就在sub[ 0 ] 到sub[ j - 1](包含sub[ 0 ] 和sub[ j -1 ])寻找以’a‘开头以’c‘结尾的两个相同的字符串, 显然从sub[ 0 ] 到 sub[ j - 1 ] 间没有这样的两个字符串,因此 next[ j ] = 0;
从sub[ 0 ] 到sub[ j - 1 ] 寻找两个完全相同的字符串的时候,第一个字符串的尾端是可以和第二个字符串的首段是可以重合的
eg:sub[ 0 ] = 'a' ; sub[ j - 1 ] = ' a ' ;因此我们要在sub[ 0 ] 到 sub[ j - 1 ] 间寻找以a开头,以a结尾的两个字符串; 即sub[ 0 ]...sub[ 3 ] 和 sub[ 3 ]...sub[ 6 ] 这两个是以'a'开头,以'a'结尾的相同的字符串;长度为4。 因此next[ j ] = 4;
通过上述方法,我们可以依次求出该子串对应的next数组的值。如下
现在我们知道了如何求next数组,接下来我们观察其数学关系,求出通式
已知: next[ j ] = k; 因此肯定会有sub[ 0 ]...sub[ k - 1 ] = sub[ x ]...sub[ j - 1 ]; 又这两个字符串完全相同,所以有 k - 1 - 0 = j - 1 - x ==> x = j - k ; 因此上文的等式可以替换为sub[ 0 ]...sub[ k -1 ] = sub[ j - k ]...sub[ j - 1 ] ;
基于这个条件,衍生了两种情况:即sub[ k ] 与sub[ j ] 的关系
1. 如果( sub[ k ] == sub[ j ] || k == -1 ) 为真 ( k == -1 时也进入的原因,在情况2中会声明)
那么sub[ 0 ]...sub[ k - 1 ] + sub[ k ] = sub[ j - k ]...sub[ j - 1 ] + sub[ j ] 为真,即 sub[ 0 ]...sub[ k ] = sub[ j - k ]...sub[ j ] 为真,KMP算法规定此时的 next[ j + 1 ] = k + 1;
2. 如果sub[ k ] != sub[ j ] 为真
那么sub[ k ] 会回退到一个特定的位置( 这个位置为 next[ k ] ) , 直到sub[ k ] == sub[ j ] 为真;但是此时还有一种情况,如果k = 0;且sub[ k ] != sub[ j ] 仍为真,那么k = next[ k ]时,我们可以注意到此时的next[ k ] = -1; 也就是说k会回退到 下标为 - 1 的位置,在这种情况下,如果我们在后续仍判断 sub[ k ] == sub[ j ] 为真还是假,那么就会访问到 sub[ -1 ] ,造成越界访问,因此我们的在第一种情况下的判断条件应为 ( k== -1 || sub[ k ] == sub[ j ] ) 为真
我们现在已经知道,已知 next[ j ] = k的情况下,如何求 next [ j + 1 ] 了。如果我们令 j = j - 1 ;就是知道了 已知 next[ j -1 ] = k 的情况下,如何求 next [ j ] 的方法
下文则是KMP算法的代码实现
算法主体:
/*
* str为主串
* sub为子串
* sup为主串被查找的位置
*/
int KMP(char* str, char* sub, int sup)//如果无法被查找则返回 -1 如果能找到返回 1 如果找不到返回 0
{
int i = sup;
int j = 0;
int lenstr = strlen(str);//str长度
int lensub = strlen(sub);//sub长度
if (str == NULL || sub == NULL)
return -1;
if (lenstr == 0 || lensub == 0)
return -1;
int* next = (int*)malloc(strlen(sub) * sizeof(int));//创建一个与子串长度相同的next数组
if (next == NULL)//确定如果程序不返回100时next数组的空间开辟成功
return 100;
To_next(sub, next);//求出next数组
while (i < lenstr && j < lensub)//保证i和j的合法
{
if ( j == -1 || str[i] == sub[j])
{
i++;
j++;
}
else
{
j = next[j]; //当不匹配时,j回到一个特定的位置
}
}
if (j >= lensub)
{
return 1;
}
else
{
return 0;
}
}
求next数组的代码:
/*
* sub为子串
* next为next数组,存放子串每个元素要回退的特定位置
*/
void To_next(char* sub, int* next)
{
next[0] = -1;
next[1] = 0; //初始化前两元素
int lensub = strlen(sub);
int j = 2; //从下标为2的位置开始,即从第三个元素开始
int k = next[1];//k为sub[1]对应的next值
//上文相当于知道了next[ j - 1 ] = k; 接下来我们依次求 next[ j ]
while (j < lensub)
{
if (k == -1 || sub[k] == sub[j])
{
next[j] = k + 1;
j++;
}
else
{
k = next[k]; //k进行回退,直到sub[k] = sub[j]或者 k = -1
}
}
}