一. 求解next
next数组的求解方法:首先第一位的next值直接给0,第二位的next值直接给1,后面求解每一位的next值时,都要前一位进行比较。首先将前一位与其next值的对应位进行比较,若相等,则该位的next值就是前一位的next值加上1;若不等,继续重复这个过程,直到找到相等某一位,将其next值加1即可,如果找到第一位都没有找到,那么该位的next值即为1。
故事助记:
唐伯虎找秋香
大家都听过,我们把这个故事改一下,改成唐伯虎找线索
,一群女郎站一排,前两位的next数组值分别是0,1,假设这就是她们手里持有的「线索」(从第三位开始玩游戏,大于等于三的位都记作[x]
),唐伯虎找第[x]
位的线索的规则如下:到底怎么找线索呢?看前一位,假设前一位是秋香的胞妹,她手里有秋香的「线索」,根据这个「线索」找到对应的那位,看是不是胞妹长的一样,如果是,就说明直接
找到了秋香,那么「胞妹手里的线索」加1就是「你的线索」
了!如果不是,继续看这位手里的「线索」,根据「线索」继续往前找,直到找到秋香,这时「秋香手里的线索」加1才是「你的线索」
了;如果找到头了还没找到,那么你的线索就是 1。
举例:
求解步骤:
1.前两位必为0,1。
2.计算第三位的时候,看第二位b的next值,为1,则把b和1对应的a进行比较,不同,则第三位a的next的值为1,因为一直比到最前一位,都没有发生比较相同的现象。
3.计算第四位的时候,看第三位a的next值,为1,则把a和1对应的a进行比较,相同,则第四位a的next的值为第三位a的next值加上1,为2。因为是在第三位实现了其next值对应的值与第三位的值相同。
4.计算第五位的时候,看第四位a的next值,为2,则把a和2对应的b进行比较,不同,则再将b对应的next值1对应的a与第四位的a进行比较,相同,则第五位的next值为第二位b的next值加上1,为2。因为是在第二位实现了其next值对应的值与第四位的值相同。
5.计算第六位的时候,看第五位b的next值,为2,则把b和2对应的b进行比较,相同,则第六位c的next值为第五位b的next值加上1,为3,因为是在第五位实现了其next值对应的值与第五位相同。
6.计算第七位的时候,看第六位c的next值,为3,则把c和3对应的a进行比较,不同,则再把第3位a的next值1对应的a与第六位c比较,仍然不同,则第七位的next值为1。
7.计算第八位的时候,看第七位a的next值,为1,则把a和1对应的a进行比较,相同,则第八位c的next值为第七位a的next值加上1,为2,因为是在第七位和实现了其next值对应的值与第七位相同。
另解(不想看可跳过):
求next数组:
先求模式串S 每一个字符前面的那个字符串的最大公共前后缀长度,将这一系列长度存成一个数组,求出来的每个长度其实就是和模式串每一个对应位置上做比较的下标
例如:模式串是ABACABC
,其最长公共前后缀长度数组为:我们将最长公共前后缀长度记作LCPSF
,现在从模式串第一个字符A开始,A的前面字符串为null,所以A之前的子串的LCPSF
是0;来到B,B的前面字符串是A,A是单独的字符不存在公共前后缀,所以长度也是0;来到A,A前面的子串是AB,LCPSF
为0;来到C,C前面的子串是ABA,LCPSF
为1;来到A,A前面的子串是ABAC,LCPSF
为0;来到B,B之前子串为ABACA,LCPSF
为1;来到C,C前面子串为ABACAB,LCPSF
为2;到此这个最长公共前后缀数组就出来了[0,0,0,1,0,1,2]
将这个数组从第二个值开始每个值加1后,得到[0,1,1,2,1,2,3]
就是将要和子串对应位置比较的下标,即为next数组。
二、求解nextval数组
掌握了上面求next数组的方法后,我们可以迅速求得模式串ABACABC的next数组为[0,1,1,2,1,2,3],现在继续求模式串ABACABC的next-val数组:
求解nextval数组是基于next数组的,模式串每一个位置的字符和其next数组值给出的下标的对应位置的数作比较,相等就取next-val中对应的next数组值作为当前位置字符的next-val值,不等就直接取当前位置字符的next数组的值作为next-val的值。
故事助记:(nextval数组第一位始终是0,从第二位开始)妈妈都会告诉小朋友不要相信陌生人,确定nextval数组的问题就可以抽象为要不要拿别人东西吃的问题,每个小朋友心里都有自己想吃的东西(当前位置数组项),并且每位小朋友手里都有一个线索(next数组项对应每位小朋友手里的线索),当别人给自己东西吃的,就拿自己线索对应的别人的吃的东西做比较,如果线索对应的东西不是自己爱吃的,那就选择吃自己的东西(保留自己的线索),否则就吃别人的东西(把别人的线索拿过来)。
求解步骤:
next-val数组第一个数直接为0。
next-val第二数:模式串第二个字符为B,对应的下标数组第二个数是1,那就是将模式串的第1个字符和B相比较,A!=B,所以直接将下标数组第二个数1作为next-val数组第二个数的值
第三个数:模式串第三个字符为A,对应下标数组第三个数为1,取其作为下标,找到模式串第1个字符为A,A=A,那取next-val的第一个数做为next-val第三个数的值,也就是0
举例:
位置 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|
模式串 | A | B | A | C | A | B | C |
next数组 | 0 | 1 | 1 | 2 | 1 | 2 | 3 |
nextval数组 | 0 | 1 | 0 | 2 | 0 | 1 | 3 |
注意,这里所有序号,第一个位置都是1开始
三、next 和 nextval 比较
next数组的缺陷举例如下:
比如主串是"aabXXXXXXXXXXXXXX",模式串"aac"
通过计算 "aac" 的next数组为012,当模式串在字符c上失配时,会跳到第2个字符,然后再和主串当前失配的字符重新比较,即此处用模式串的第二个a和主串的b比较,即 "aabXXXXXXXXXXXXXX"vs"aac",显然a也不等于b。然后会跳到1接着比,直到匹配成功或者匹配失败主串后移一位。
而"aac"的nextval数组为002 当在c失配时会跳到2,若还失配就直接跳到0,比next数组少比较了1次。
在如果模式串很长的话,那可以省去很多比较,因此使用nextval数组比next数组高效。
四、问题思考:
在模式匹配的KMP算法中,求模式的next数组值(也称为KMP算法中失败函数)定义如下:
next数组的定义
(1)当j=1时,为什么要取next[1]=0?
答:当模式串第一个字符与主串中某字符不匹配时,主串指针应移至下一字符,再和模式串第一个字符比较。(next[1]=0 表示模式串中已没有字符可与主串中当前字符 s[i] 比较)
(2)为什么要取 max{k},k最大是多少?
答:当主串中第 i 个字符与模式串中第 j 个字符不匹配时,若主串 i 不回溯,则假定模式串中第 k 个字符与主串中第 i 个字符比较,k 值应满足条件 1<k<j,并且 'p1...pk-1' == 'pj-k+1...pj-1',即模式串向后移动的距离为 k,k值可能有多个,为了不使移动产生丢失可能的匹配,k要取最大值,max{k}表示移动的最大的距离,k的最大值为 j-1。
(3)其它情况是什么?为什么取 next[j] = 1?
答:以上两种情况的不匹配,主串指针不回溯,在最坏的情况下,模式串从第 1 个字符开始与主串第 i 个字符比较,以便不丢失可能的匹配。
网友评论