六(三续):KMP 算法之总结篇(必懂 KMP)
第二部 分、next 数组求法的来龙去脉与 KMP 算法的源码
本部分引自个人此前的关于 KMP 算法的第二篇文章:六之续、由 KMP 算法谈到 BM 算法。
前面,我们已经知道即不能让 P[j]=P[next[j]]成立成立。不能再出现上面那样的情况啊!即 不能有这种情况出现:P[3]=b,而竟也有 P[next[3]]=P[1]=b。
正如在第二篇文章中,所提到的那样:“这里读者理解可能有困难的是因为文中,时而 next,时而 nextval,把他们的思维搞混乱了。其实 next 用于表达数组索引,而 nextval 专 用于表达 next 数组索引下的具体各值,区别细微。至于文中说不允许 P =P[next[j] ]出现,
是因为已经有 P =b 与 S 匹配败,而 P[next ]=P1=b,若再拿 P[1]=b 去与 S 匹 配则必败。”--六之续、由 KMP 算法谈到 BM 算法。
又恰恰如上文中所述:“模式串 T 相对于原始串 S 向右移动了至少 1 位(移动的实际 位数 j - next[j] >=1)
”
。ok,求 next 数组的 get_nextval 函数正确代码如下:
1. //代码 4-1
2. //修正后的求 next 数组各值的函数代码
3. void get_nextval(char const* ptrn, int plen, int* nextval)
4. {
5. int i = 0;
6. nextval[i] = -1;
7. int j = -1;
3. 进入循环的 if 部分,++i,++j,i=2,j=0,因为 ptrn[i]=ptrn[j]=a,所以 nextval[2]=nextval[0]=-1;
4. i=2, j=0, 由于 ptrn[i]=ptrn[j],再次进入循环 if 部分,所以++i=3,++j=1,因为 ptrn[i]=ptrn[j]=b,所以 nextval[3]=nextval[1]=0;
5. i=3,j=1,由于 ptrn[i]=ptrn[j]=b,所以++i=4,++j=2,退出循环。
这样上例中模式串的 next 数组各值最终应该为:
图 4-1 正确的 next 数组各值 next 数组求解的具体过程如下:
初始化:nextval[0] = -1,我们得到第一个 next 值即-1.
图 4-2 初始化第一个 next 值即-1
i = 0,j = -1,由于 j == -1,进入上述循环的 if 部分,++i 得 i=1,++j 得 j=0,且 ptrn[i] !=
ptrn[j](即 a!=b)),所以得到第二个 next 值即 nextval[1] = 0;
图 4-3 第二个 next 值 0
上面我们已经得到,i= 1,j = 0,由于不满足条件 j == -1 || ptrn[i] == ptrn[j],所以进入循 环的 esle 部分,得 j = nextval[j] = -1;此时,仍满足循环条件,由于 i = 1,j = -1,因为 j ==
-1,再次进入循环的 if 部分,++i 得 i=2,++j 得 j=0,由于 ptrn[i] == ptrn[j](即 ptrn[2]=ptrn[0],
也就是说第 1 个元素和第三个元素都是 a),所以进入循环 if 部分内嵌的 else 部分,得到 nextval[2] = nextval[0] = -1;
图 4-4 第三个 next 数组元素值-1
i = 2,j = 0,由于 ptrn[i] == ptrn[j],进入 if 部分,++i 得 i=3,++j 得 j=1,所以 ptrn[i] ==
ptrn[j](ptrn[3]==ptrn[1],也就是说第 2 个元素和第 4 个元素都是 b),所以进入循环 if 部 分内嵌的 else 部分,得到 nextval[3] = nextval[1] = 0;
图 4-5 第四个数组元素值 0
如果你还是没有弄懂上述过程是怎么一回事,请现在拿出一张纸和一支笔出来,一步一 步的画下上述过程。相信我,把图画出来了之后,你一定能明白它的。
然后,我留一个问题给读者,为什么上述的 next 数组要那么求?有什么原理么?
提示:我们从上述字符串 abab 各字符的 next 值-1 0 -1 0,可以看出来,根据求得的 next 数组值,偷用前缀、后缀的概念,一定可以判断出在 abab 之中,前缀和后缀相同,即都是 ab,反过来,如果一个字符串的前缀和后缀相同,那么根据前缀和后缀依次求得的 next 各 值也是相同的。
5、利用求得的 next 数组各值运用 Kmp 算法
Ok,next 数组各值已经求得,万事俱备,东风也不欠了。接下来,咱们就要应用求得的 next 值,应用 KMP 算法来匹配字符串了。还记得 KMP 算法是怎么一回事吗?容我再次引用 下之前的 KMP 算法的代码,如下:
1. //代码 5-1
2. //int kmp_seach(char const*, int, char const*, int, int const*, int pos) KM P 模式匹配函数
3. //输入:src, slen 主串 4. //输入:patn, plen 模式串
5. //输入:nextval KMP 算法中的 next 函数值数组
6. int kmp_search(char const* src, int slen, char const* patn, int plen, int co nst* nextval, int pos)
图 5-2 第一步,S[3]与 P[3]匹配失败
第 二 步 : S[3] 保 持 不 变 , P 的 下 一 个 匹 配 位 置 是 P[next[3]] , 而 next[3]=0, 所 以 P[next[3]]=P[0],即 P[0]与 S[3]匹配。在 P[0]与 S[3]处匹配失败。
图 5-3 第二步,在 P[0]与 S[3]处匹配失败
第三步:与上文中第 3 小节末的情况一致。由于上述第三步中,P[0]与 S[3]还是不匹配。
此时 i=3,j=nextval[0]=-1,由于满足条件 j==-1,所以进入循环的 if 部分,++i=4,++j=0,即主串 指针下移一个位置,从 P[0]与 S[4]处开始匹配。最后 j==plen,跳出循环,输出结果 i-plen=4(即 字串第一次出现的位置),匹配成功,算法结束。
图 5-4 第三步,匹配成功,算法结束 所以,综上,总结上述三步为:
1. 开始匹配,直到 P[3]!=S[3],匹配失败;
1. //copyright@2011 binghu and july 2. #include "StdAfx.h"
9. void get_nextval(char const* ptrn, int plen, int* nextval)
10. {