不用回溯指针的关键:
1.模式串移动
2.最大相同前后缀
匹配到指针位置时模式串与主串不匹配。按照朴素的串匹配思想,应当将指针回溯到主串当前匹配其实位置的下一个位置上再重新与模式串其实位置开始匹配。
KMP算法则是考虑当前模式串已经匹配的前面部分有什么特点:不难发现,已经匹配部分ABBAB的存在前缀AB和与之相同的一个后缀AB。
于是,显然不用采用朴素的串匹配思想将指针回溯到主串其实匹配位置的下一个位置(即B),模式串也不用从头重新匹配。
将模式串前缀“拖动”到后缀位置上(前缀与后缀相同),可以发现模式串再一次与主串相匹配。(可以证明:模式串“拖动”过程中停留在其他位置上时不会与主串发生匹配)
在“拖动”过程中,如果模式串串尾已经超出主串串尾位置,则不用继续匹配,直接判定模式串无法得到匹配。
由于字符串是以字符数组形式存储在内存中的,模式串的“拖动”并不好实现,以上图示是为了方便比较模式串与主串位置,实际上匹配时只用考虑模式串。
如上图所示:
1)刚开始匹配时,如果模式串第一位与主串当前位不匹配,指针指向主串当前位下一位。
2)如果模式串第一位与主串匹配,第二位发生不匹配,则指针仍指向主串发生不匹配的当前位,转而用模式串第一位来和主串当前位匹配。(回到情况1))
3)如果模式串前两位都匹配,第三位发生不匹配,由于前两位AB不存在相同的前后缀,指针不改变,用模式串第一位来和主串当前位匹配。
4)模式串前三位匹配,第四位开始不匹配,由于模式串前三位ABA存在相同的前后缀A子串,于是指针不用改变,用模式串第二位(选择模式串的已匹配部分最长相同前后缀子串长度+1位)来和主串当前位匹配。
5)…
…
很明显,上述过程中,不会发生指针回溯,模式串失去匹配时也不用真的“拖动”,而是选择模式串已经匹配部分的最长前缀(具有相同后缀且最长)的下一位与主串当前位继续匹配。
参考自:https://www.bilibili.com/video/BV1jb411V78H?from=search&seid=7398209742332799990
上面分析过程中,模式串每个位置前面的子串的最长前缀长度S是问题的关键。如何使用程序来计算每个位置的S呢?通常用数组next存储模式串每个位置的S值,下面是计算模式串next数组的完整程序:
void GetNext(char ch[],int length,int next[])
{//1ength为串ch的长度
next[1]=0;
int i=1,j=0;
while(i<length){//已有next[1]=0,还需求得length-1个next数组元素
if(j==0||ch[i]==ch[j])
next[++i]=++j;
else j=next[j];
}
}
程序十分精简,但是理解起来却不容易。
next[0]规定等于0,next[1]=1。(这里默认模式串下标从1开始,从0开始的话,next[0]=-1)
next[j]-1就是j号位置前面的子串的最长前缀长度。
首先明白一点:i是指针指向的主串的当前位置。
假设现在要计算next[17],已知next[16]=8,即模式串1-7号位置的前缀和9-15号位置的后缀相同。
【注意】next[16]=8,表明16号元素前面的15个元素(下标从1开始计算)构成的串有长度为8-1的最长公共前后缀。因为Next数组其实经过了加1并右移的过程。
那么要计算next[17]首先要判断16号位置元素是否和8号位置元素相同,这样,next[17]显然等于8+1=9.
即if(ch[i]==ch[j]) next[++i]=++j; (此时i=16,j=8)
反之,ch[16]≠ch[8]时,怎么判断模式串前16位最长前缀长度呢?
由代码可知,ch[i]≠ch[j]时,j=next[j];
假设next[8]=4,即j=4,则如上图所示的四个部分是完全相同的。
已知1,2,3位和13,14,15位相同
于是,要找前16位最长前缀就要判断第4位和第16位是否相同。
即if(ch[i]==ch[j]) next[++i]=++j; (此时i=16,j=4)
如果ch[16]≠ch[4],则继续递归。
此时判断第16位是否和第2位(假设next[4]=2)相同。
参考自:https://www.bilibili.com/video/BV16X4y137qw?from=search&seid=7398209742332799990
nextval数组
考虑如下情况
这里模式串下标从0开始,next[0]=-1,next[1]=0。
while(i<len)//求next数组,模式串下标从0开始
{
if(j==-1||str[i]==str[j])
{
i++;j++;
next[i]=j;
}
else j=next[j];
}
while(i<len)//求nextval数组,模式串下标从0开始
{
if(j==-1||str[i]==str[j])
{
i++;j++;
if(str[i]!=str[j])next[i]=j;
else next[i]=next[j];
}
else j=next[j];
}
next数组转化为nextval数组
参考自:https://www.icode9.com/content-1-669421.html