随着数据结构考试的紧邻,突击复习又成为学生们的首要目标(笑)
做到“串”课后习题的时候,突然发现我还没有掌握KMP算法!!!
于是就在网上狂找资料,同时结合课本,算法思想是明白了,但要命的是这next数组、nextval数组真心不好求,我就在想怎样才能将求数组的方法搞的浅显易懂一些,现将方法分享如下:
对了,本篇文章五成以上算是转载内容。
在介绍计算方法之前,请各位看官移步:阮一峰老师的讲解
阮老师从侧面说明了KMP算法的实现过程。
首先我要整理一下我所了解的KMP(干货还是有的):(如您没有时间请直接跳到第三大段找计算方法)
我把一个公式放在前面“模式串要移动的长度=已进行成功匹配的模式串字符长度(j)-对应的模式串的部分匹配值”(未详讲,请参看阮一峰老师的博客)
那么什么是“模式串的部分匹配值”呢?
我们在这里定义两个概念:“前缀”、“后缀”
例如:对于给定的模式串1.aba,其前缀为{a}、{ab},其后缀为{ba}、{a};2.a,其前缀为空,后缀也为空。
即:前缀为一个字符串除掉最后一个字符后剩余字符的顺序排列、后缀则为除掉第一个字符后剩余字符的顺序排列。
在了解前缀、后缀之后,我们给出“部分匹配值”的定义。
定义:一个字符串的部分匹配值为该字符串前缀、后缀中相同部分的字符(最大)数目。
下面验证该定义的合理性(偏离主题了,就不验证了)。
不是看不懂书上(数据结构C语言版)关于next数组的定义吗?接下来我用上述给出的定义将next数组进行解释。
书上的定义如下:
1.当j=1时,next[1]=0这个应该很好理解吧,模式串的第一个字符就不匹配,当然是从头开始比较下去了(为什么为0请看KMP算法的实体);
2.来看第二个1
这是什么意思呢?我们回首一下刚才给出的“前缀”“后缀”,是不是就能得到“前缀”与“后缀”相同的(最大数目)(正是部分匹配值)=k-1
即k=next[j]=部分匹配值+1
这样,我们就得到了next数组的计算方法了(涵盖了第三种情况)
下面我们来验证一下:
以书上给出的模式串为例:abaabcac
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
a | b | a | a | b | c | a | c |
0 | 1 | 1 | 2 | 2 | 3 | 1 | 2 |
next[1]=0;
next[2]=部分匹配值+1=“a”的“前缀”与“后缀”相同的最大数目+1=0+1=1;
……
next[6]=部分匹配值+1=“abaab”的“前缀”与“后缀”相同的最大数目+1=2+1=3;
……
完全正确!
next数组计算出来了,那么nextval数组是怎么得到的呢(无关算法)?
以aaaab为例,按我们上述给出的计算方法,求得其next数组为:
1 | 2 | 3 | 4 | 5 |
a | a | a | a | b |
0 | 1 | 2 | 3 | 4 |
而我们在实际匹配过程中,发现(书上讲的我不想看= =)
如果在aaaab的第二个位置失配,说明主串的失配位置一定不是“a”,那么根本不需要同模式串的第一个字符“a”进行比较,但next[2]=1,这样跟我们的预期不符,nextval数组就是基于这种情况下被提出的。
nextval数组的计算方法为:
公式:若t[j]=t[next[j]],则nextval[j]=nextval[next[j]],否则保持不变;
人话:先算出模式串的next数组,依次从第二个开始向后更新,如果依照next数组指向的字符与所求字符是相同的话(想一想上述给出的例子“aaaab”),就将所求字符的next值更新为本来该字符next数组指向的字符的next值;如果不相同,则保持不变。
仍以aaaab为例:
1 | 2 | 3 | 4 | 5 |
a | a | a | a | b |
0 | 1 | 2 | 3 | 4 |
0 | 0 | 0 |
0 | 4 |
nextval[1]=0;
nextval[2]=0;因为t[1]=“a”,t[2]=“a”,这两个字符相同,所以更新为0;
……
nextval[4]=0;因为t[4]=“a”,t[3]=“a”,字符相同,所以更新为0(当然是先求nextval[3]了笨蛋);
nextval[5]=4;因为t[5]=“b”,t[4]=“a”,字符不相同,所以保持不变仍然为4;
怎么样,是不是很简单。
当然,我所讲的并没有触及到核心内容:“部分匹配值”提出的原因,next、nextval数组的函数实现。如果通过阅读本文能给你带来收获,如此甚好。