1.为什么要用 KMP算法?
在每一趟匹配过程中出现比较字符不等时,不需要回溯指针,而是利用已经得到的“部分匹配”的结果将模式串向右“滑动
尽可能远的距离后,继续比较。
先放代码:
int Kmp (sq s,sq p,int next[]){
int c = 0; //主串移动变量
int d = 0;
while(c < s.lenght && d < p.lenght) //合法长度之内
{
if(d == -1 || s.string[c] == p.string[d])
{
/* d == -1 或者匹配成功就累加继续比较下一个*/
c++;
d++;
}
else
d = next[d]; //利用next数组进行移动,通过移动最长真前缀,避免不必要的回溯
}
if (d == p.lenght)
return c-p.lenght;//匹配成功,返回和模式串中第一个字符等的字符在主串的序号
else
return -1; //匹配失败
}
2.next[]是什么?
当模式中第j个字符与主串中相应字符“失配”时,在模式中需重新和主串中该字符进行比较的字符的位置。怎么确定该位置呢?就是要用的next[]数组,进行位置的确定。
3.构造next数组的过程
先放代码:
void GetNext (sq q,int next[]){
int i = 0,j = -1;
/*next数组,默认为-1,避免长串与短串首字符不匹配时,短串首字符为,next[0]=0,造成长短串匹配位置不更新,进入死循环*/
netx[0] = -1;
while (i < q.lenght){
/*自动匹配过程*/
if (j == -1 || q.string[i] == q.string[j]){
//(与首字符进行匹配 || 两个字符相等)
/*当与首字符匹配时,因为首字符的next[j]=-1,此时i++;j++; 主串后移一位,模式串也刚好从首字符开始进行匹配 */
i++;
j++;
next [i] = j;
}
else
j = next[j];
}
}
构造next数组,可以理解为模式串自己与自己比较,寻找最大真前缀,让主串与最大真前缀的下一位进行匹配。原理解释如下:
这里我们把模式串标记成两个,方便表述。我们拿T2去与T1匹配。我们发现在T2的0号位与T1的3号位匹配成功,因为T2与T1是同一个串,那么T1的0号位一定和T2的0号位一样。所以,我们可以利用这个原理,来计算每一个位置的最大真前缀。
再如:
当我们发现T2的0号位和1号位分别和T1的3、4号位匹配,那么请问T1的1号位是什么颜色?
答案很简单,是蓝色,因为本身就是一个串。
我们看到,每匹配成功一项,最长真前缀就+1
若当前最长真前缀为next[i],匹配成功后next[i+1]=next[i]+1
如果模式串如下图所示
发生不匹配,T2回溯, T1不动
回溯多少呢?我们看到T2需要从4号位回溯到1号位。这里next[4]=1,说明最长的真前缀长度是1, 我们刚好要比较最长真前缀后面的字符,想回溯到标号为1的位置。由于字符串的下标从0开始,也就相当于回溯到了next[4]的位置,然后重新比较j = next[j]
简单来说,当T2的j号位
与T1的i号位
匹配不成功时,T2需要回溯到j号的最大真前缀的下一个位置
,让其与T1的i号位进行比较。匹配成功,则各自后移,匹配不成功,继续寻找最大真前缀,进行匹配。
下面再深入分析匹配成功的情况
如图,我们在进行T1的9号位与T2的3号位匹配时,实际在计算T1的10号位的next值。
next[10] = next[9]+1
因为next[9]的值既然比较过了,就已知是3,我们观察到,此时正是T2的3号位与之进行匹配
即:next[i+1] = next[i] + 1= j + 1;
再次简单来说,计算当T1的i+1号位
的不匹配时下次进行匹配的位置时,若 i号位
与T2的j号位
进行匹配成功,说明已经至少有j长度的真前缀了,这里只需进行+1操纵,使其变的更长。
我们再看一下匹配成功的代码部分:
if (j = = -1 || q.string[i] == q.string[j]){
//(与首字符进行匹配 || 两个字符相等)
i++;
j++;
next [i] = j;
}
我们发现在代码中,再给next[i]赋值之前,i 和 j 已经完成了自加1,那就直接写出
next [i] = j;
4.为什么设置next[0]=-1?
当回溯到0号位,依然匹配不成功,我们把0号位是next[j]赋值给j,使得下一次进入if语句满足条件,主串和模式串分别后移,继续匹配。若按照寻找next[j]的原理,把next[0]=0,那么会出现一直匹配不成功,形成死循环。
5.字符串从1的位置进行存储,代码有影响吗?
答案肯定是没有。
在我们手动书写next[j]的时候要注意,此时的next[j]的值不仅仅是最长前缀的长度,还得+1
两种区别如下:
在发生不匹配进行回溯时,我们说因为串从0开始标记,所以回溯到最大真前缀的位置继续进行匹配。代码为:i = next[j]
。当我们把串从1开始标记,i,j的起始位置都从下标开始进行存储,所以在计算机里,代码实现不变,只需在人工计算时注意就好。