简介:KMP算法是D.E.Knuth、J,H,Morris 和 V.R.Pratt 发表的一个模式匹配算法,可以大大避免重复遍历的情况(即消除了主串指针回溯的),从而提高算法效率
next数组的求解
我认为KMP算法最难理解的就是next数组的求解,理解了next数组的求解KMP算法就了解的差不多了。
在此之前,我们需要了解几个概念:
前缀:包含首字符不包含尾字符的全部子串
后缀:包含尾字符不包含首字符的全部子串
前缀表:它记录了模式串与主串 (文本串)不匹配的时候,模式串应该从哪里开始重新匹配。即所有包含首字符的子串的最大相等前后缀的长度所构成的数组,即next数组(也有人叫做Prefix)
例如下图,黄色标出的就是一个前缀子串,绿色标出的就是一个后缀子串,黄色或绿色的长度就是 j 指针运动到 f 字符时最大相等前后缀的长度。
下文中 i 表示前缀子串的末尾,同时 i+1 还代表着最大相等前后缀的长度,j 表示后缀子串的末尾
现在可以实现next数组了,next数组的求解需要做以下几件事:
1.初始化
2.前后缀相等时,更新next数组
3.前后缀不相等时, i 回退
void getNext(char* ModeString,int* next)
{
//初始化
int i = 0, j = 1;
next[0] = 0;
while (j < strlen(ModeString))
{
//当前后缀相等时,和当i指向数组头时,就要更新next[j]的值
if (i == 0 || ModeString[j] == ModeString[i])
{
if (ModeString[j] == ModeString[i])
i++;
next[j] = i;
j++;
}
//前后缀不相等时,i回退
//这行代码最难理解,回退的位置是next数组求解的关键
else i = next[i - 1];
}
}
1.初始化
使 i 指向第一个元素,j 指向第二个元素,因为当字符串只有一个元素时,既没有前缀也没有后缀,所以其最大相等前后缀长度为0,因此将next[0]赋值为0
注:有些next求解的实现会将 i 赋值为-1,j 赋值为0,next[0]赋值为-1,这就是将next数组整个往回移动了一位,与本文的方法大同小异
2.前后缀相同时,更新next数组
首先我们要理解当ModeString[i]==ModeString[j]时,一定有ModeString[i-1]==ModeString[j-1],ModeString[i-2]==ModeString[j-2]...ModeString[0]==ModeString[j-i],因为当前后缀不相同时,i 会回退,所以 i 走过的位置一定满足ModeString[i]==ModeString[j],因此 i+1 还可以可以代表最大相等前后缀的长度,所以当ModeString[i]==ModeString[j]时就将 i+1 赋值给next[j]即可。
但还有一种特殊情况,就是当 i 退无可退的时候(即i == 0时),当 i 退无可退时,说明该此时 j 所指向的字符的最大相等前后缀长度为0,此时就不需要在给 i +1 了直接将 i 赋值给next[j]就可以了,所以在 i++ 前加一个判断语句,当i==0&&ModeString[i]!=ModeString[j],i 不做递增操作。
3.前后缀不相同时,i 回退
i = next[i-1]第一次看到这行代码一定会很懵,为什么 i 要回退到 next[i-1]的位置,而不是一步一步递减回去呢?
首先,我们要知道 i 的含义不仅仅代表着前缀子串的末尾,还代表着最大相等前后缀的长度-1,所以 i 的值不能随便的加减,会使前后缀的匹配出现错误,例如下图:
当i 和 j 不匹配时,如果 i 回到 i-1 的位置
这时,ModeString[i]==ModeString[j],i == 2,于是将next[j]赋值为 3,最长前后缀变为下图的样子
但很明显aab和abb并不相等,所以回退的位置不能是 i-1。我们应该让 i 回退到一个位置,这个位置可以保证模式串中 0 ~ i-1 的字符和 j-i ~ j-1 的字符相等
将 i 赋值为next[i-1]就满足这样的条件,下面是证明过程:
前文我们已经知道,当ModeString[i]==ModeString[j]时,一定会有字符0 ~ i和(j-i)~ j都相等,并且next[i]位置记录的是,i 位置的后缀子串,所对应的最大相等前缀子串的长度。所以有下图:
因为 i 走过的的位置一定满足ModeString[i]==ModeString[j],所以当ModeString[i]!=ModeString[j]时,0 ~ i-1和 j-i ~ j-1的字符相等,即上图标出的前后缀是完全相同的。next[i-1]位置的记录的就是前缀子串的最长相等前后缀的长度,我们假设next[i-1]的值为3(这个值可以是任意非负数),我们很容易得出前缀是下图这样的(红色标出的子串是相等的)
这样很容易可以推出整个模式串是这样的
这样 i 要回退的位置就一目了然了,就是下图绿色标出的位置,而这个位置的下标不正好就是前缀子串的最大相等前后缀的长度嘛,也就是next[i-1]位置的值
如果该位置的字符依然不匹配,就继续回退。
以上就是我对KMP算法中求解next数组的理解,有错误的地方,还请不吝赐教。