参考视频
慕课数据结构——KMP算法
完整代码
int KMP(string m,string p,int next[])
{
int i=0,j=0;
while(i<m.length()&&j<p.length())
{
if(j==0||m[i]==p[j])
{
i++; //继续对下一个字符比较
j++; //模式串向右滑动
}
else j=next[j];
}
if(p[j]=='\0') return (i-j+1); //匹配成功返回下标
else return -1; //匹配失败返回-1
}
KMP算法的基础过程
模式串的next值的求解思想
1)主串指针i不回溯
因为当主串匹配到i失配时,i前面的字符和模式串j前面的字符都是相等的,现在我们要把模式串往后边尽可能多地挪一挪,假设挪动k位,使得i前面的已经匹配好的部分串能够得到最大的利用。要知道,i该从哪个j开始继续匹配当然:为了防止漏掉,1<k<j
2)将模式串移动k位后,即j=next[j]。
3)模式串的next值与主串S无关而只与模式串T有关:因为当主串匹配到i失配时,i前面的字符和模式串j前面的字符都是相等的。本质上是在对模式串本身做研究,故与主串无关。
5)具体应该移动到哪里?
模式串的T[j]与主串的S[i]失配了,S[i]!=T[j],模式串向后移动k位后,模式串的T1到Tk-1与主串的Si-k+1到Si-1相等,因为当主串匹配到i失配时,i前面的字符和模式串j前面的字符都是相等的。故Si-k+1到Si-1等于模式链中的Tj-k+1到Tj-1。
T1到Tk-1是T1到Tj-1的真前缀串
而Tj-k+1到Tj-1是T1到Tj-1的真后缀串
所以实际上是找T1到Tj-1这个串中的真前缀串和真后缀串相等的时候,选取最大的那个子串(后缀或者前缀都是串的子串)的长度再加上1就是新的j。
Si=6的时候匹配失败,这个时候Si应该与哪个Tj进行匹配才能保住前面已经匹配好的部分呢?Si=6和第一个e比较
j前面的串a b c d e e a b c d ,长度相等的最大真前缀和真后缀是a b c d。长度为4,那就让j=5,(i,j从1开始)即Si=6和Tj=e比较。
后缀和前缀相等,就可以在失配时利用之。
求next值的具体算法
int next[10];
char p[10];
int j=1,k=0;
next[1]=0;
while(j<10)
{
if(k==0||p[j]==p[k])
{
j++;
k++;
next[j]=k;
}
else
k=next[k];
}
next数组的求法是关键。
next数组存放的是模式串中,每个字符失配时,模式串该跳去的那个id,以达到利用最大公共前后缀的目的。
如图: 在p[10]=d时失配,根据对d之前的部分串的最大公共前后缀研究得知,模式串该回到p[3]去,所以,next[10]=3.
具体推导:
j有三个去处1)j++
,2)j=0
,3)j=next[j]
在next数组的求解过程中:
初始条件:
k留在前缀,j留在后缀。
k=0,j=1;
next[1]=0,//意为在第一个字符就失配,主串得往后面走.
if(k==0||p[j]==p[k])
{
j++;
k++;
next[j]=k;
}
执行这个if结构有两种情况
1)k==0
j之前的部分串前后缀失配导致k=0,因而理所当然地j++,k++,进行下一步判断,同时next[j]=0
2)p[j]==p[k]
说明p[k]和p[j]匹配成功了,那么假如在p[j+1]失配,模式串就该回到k+1去。也是j++,k++.
else
k=next[k];
执行这个else分支
说明k既不等于0,p[j]也不等于p[k],已经匹配了一段,到此为止失配,则k应该回到?
这里的行为类似主框架中j=next[j]
的行为。虽然这里失配了,难到k就一定要回到0,重新开始吗?不是的,因为这里失配之前的已经匹配的部分可以利用。
这里又相当于把前缀当模式串,后缀当主串进行匹配。故k=next[k]