KMP算法优于BF算法的地方是指针不回溯,利用已经比较过的部分,将模式串向右移动尽可能远的距离,在继续比较。
KMP算法的核心就是构建一个next[]数组,以这个数组来决定移动的距离。
在此之前先引入 公共前缀后缀这个定义:
以”ABCDABD”为例,进行介绍:
- ”A”的前缀和后缀都为空集,共有元素的长度为0;
- ”AB”的前缀为[A],后缀为[B],共有元素的长度为0;
- ”ABC”的前缀为[A, AB],后缀为[BC, C],共有元素的长度0;
- ”ABCD”的前缀为[A, AB, ABC],后缀为[BCD, CD, D],共有元素的长度为0;
- ”ABCDA”的前缀为[A, AB, ABC, ABCD],后缀为[BCDA, CDA, DA, A],共有元素为”A”,长度为1;
- ”ABCDAB”的前缀为[A, AB, ABC, ABCD, ABCDA],后缀为[BCDAB, CDAB, DAB, AB, B],共有元素为”AB”,长度为2;
- ”ABCDABD”的前缀为[A, AB, ABC, ABCD, ABCDA, ABCDAB],后缀为[BCDABD, CDABD, DABD, ABD, BD, D],共有元素的长度为0
示例: 主串:S: ABCABCDE 模式串 :T: ABCD
我们之所以会这样移动是因为我们的在大脑中模拟了字符的比较,显而易见B,C都不是A。等于说我们又计算或者说思考了两次,这就是省略了两步的BF算法,那么我们如何只思考一次就直接得到要将模式串移动到哪呢?这就是KMP算法的精髓,我们要利用字符串的前后缀公共长度,一个事实是:主串中已经匹配过的部分肯定也是模式串的一部分 所以如果模式串中没有和其前缀相同的字符或字符串,通俗一点说就是 看看有没有和以首字符为起始的相同的一个或多个字符,如果没有那就将模式串的首字符和上一次主串中未匹配的字符进行比较。 如上图。
如果有相同的字符则将模式串首字符移至 上一次 该字符和主串中字符匹配的位置 看下面示例:
主串 S: ABABACAD 模式串 T:ABAC 一: ABABACAD 二:ABABACAD ABAC ABAC
因为T[2]==T[0] T[2]==S[2],所以T[0]==S[2]。因为T[0]和S[2]已经相等了所以当第一次比较至S[3]不匹配时,i不动,将j移动到T[0]后一位。 规定下标从1开始我们可以得到next[4]=2;这就是next值的基本计算思想;规定next[1]=0;可以通过递推的方式计算所有next值;
代码实现如下:
void get_next(string T,int next[])
{
i=1;next[1]=0;j=0;
while(i<T.size())
{
if(j==0||T[i]==T[j])
{
i++;
j++; //j是公共长度;
next[i]=j;
}
else j=next[j]; //回溯到最长公共前后缀的后一位。
}
}
重点是j=next[j]; 可以想象成模式串自己匹配自己, 当不匹配时,j指针回溯,和上述思想一样。