KMP 模式匹配算法是为了解决在一个主串中去寻找子串的问题,在没有 KMP 算法之前,人们采用那种人们最容易想到的以字串按照主串从首位开始一位一位后移的方法,但是这种方法很费时间,例如下面例子:
当比较到最后一位的时候,发现该段不匹配,接下来指针又要回溯到开头,开始第二个遍历,但是很明显第二个也不会存在匹配的情况,但是这种算法是无法判别的,所以就需要一种非常高效的匹配算法,于是,就衍生出了 KMP 算法,要熟悉 KMP 算法,就必须要知道模式串的 next 数组怎么推导。
next 数组的推导
以模式串 abcabx 为例:
因为在一般数据结构中,在对 next 数组推导时,数组下标都是从 1 开始,而 next[0]一般保存模式串的元素个数。next 数组值推导公式是
值 = 前后缀最大字符匹配数 + 1
步骤是:
1.当 j 为 1 时,因为 j = 1 指向的模式串位置前面没有字符,所以默认为 0
2.当 j 为 2 时,因为 j = 2 指向的模式串位置前面只有一个字符 a,前后缀匹配数为 0,值为 1
3.当 j 为 3 时,因为 j = 3 指向的模式串位置前面有 ab,前后缀匹配数为 0,值为 1
4.当 j 为 4 时,因为 j = 4 指向的模式串位置前面有 abc,前后缀匹配数为 0,值为 1
5.当 j 为 5 时,因为 j = 5 指向的模式串位置前面有 abca,前缀 a 和后缀 a 匹配,前后缀匹配数为 1,值为 2
6.当 j 为 6 时,因为 j = 6 指向的模式串位置前面有 abcab,前缀 ab 和后缀 ab 匹配,前后缀匹配数为 2,值为 3
程序求取 next 数组的示例:
//T 为模式串
void get_next(char *T,int *next)
{
int i,j;
i = 1;//用来遍历模式串
j = 0;//用来回溯
next[0] = strlen(T);
next[1] = 0;
while (i < next[0])
{
if(j == 0 || T[i-1] == T[j-1])
{
++i;
++j;
next[i] = j;
}
else
{
j = next[j]; /*若字符不协调,则 j 值回溯*/
}
}
}
以 next 数组进行 KMP 匹配推导算法程序
/**
* @brief KMP 模式匹配算法
*
* @param S 主串
* @param T 字串
* @param pos 从主串的第几个字符开始匹配
* @return int 成功返回匹配的位置,失败返回-1
*/
int Index_KMP(char *S,char *T,int pos)
{
int i = pos; //主串下标
int j = 0; //子串下标
int next[255]; //next 数组
get_next(T,next); //计算 next 数组
while (i < sizeof(S) && j < sizeof(T))
{
if(j == 0 || S[i] == T(j))
{
if(j != 0 || S[i] == T(j))
j++;
i++;
}
else
{
j = next[j+1]; //j 值回溯,因为 next[0]保存的是模式串长度,所以 j+1;
}
}
if(j == sizeof(T))
return i - sizeof(T[0]);
else
return -1;
}
KMP 改进算法的 nextval 数组推导
因为上面通过 next 数组的方式去实现 KMP 算法在一些情况下还是存在缺陷的,例如一个主串为 aaaabcde,模式串为 aaaaax,模式串的 next 数组为 012345,那么匹配的算法步骤就是
当 i 和 j 都为 5 时发现不匹配,这个时候 j 回溯,next[5] = 4;,回溯到 4,然后因为上一 次 a 就于 b 不匹配,回溯到 4 还是 a,自然是还不匹配,按照人为理解来说,模式串因为存在很大相同的,不需要一步一步回溯,直接 i 后移 j 回溯向后比较就行了,也就是上图的 2345 步骤是多余的,那么要解决这种问题,就需要进一步对 next 数组进行升级,形成 nextval 数组,下面将给出 nextval 数组的推导程序示例:
//T 为模式串
void get_next(char *T,int *nextval)
{
int i,j;
i = 1;//用来遍历模式串
j = 0;//用来回溯
nextval[0] = strlen(T);
nextval[1] = 0;
while (i < next[0])
{
if(j == 0 || T[i-1] == T[j-1])
{
++i;
++j;
if(T[i-1] != T[j-1]) //如果单纯只是因为 j == 0 进来的
nextval[i] = j;
else
nextval[i] = nextval[j];
}
else
{
j = next[j]; /*若字符不相同,则 j 值回溯*/
}
}
}
KMP 的改进算法就是改进了 next 数组,优化了其中的回溯值,其他的算法匹配步骤依然不变。