kmp算法之nextval(转载 )

本文详细介绍了KMP算法中next和nextval数组的计算方法。通过实例解析next数组的递推求解过程,以及两种求解nextval数组的方法,帮助读者深入理解KMP算法的核心思想。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

首先看看next数组值的求解方法。 
       例如: 模式串abaabcac     next01122312     nextval       next数组的求解方法是:第一位的next值为0,第二位的next值为1,后面求解每一位的next值时,根据前一位进行比较。首先将前一位与其next值对应的内容进行比较,如果相等,则该位的next值就是前一位的next值加上1;如果不等,向前继续寻找next值对应的内容来与前一位进行比较,直到找到某个位上内容的next值对应的内容与前一位相等为止,则这个位对应的值加上1即为需求的next值;如果找到第一位都没有找到与前一位相等的内容,那么需求的位上的next值即为1。 
       看起来很令人费解,利用上面的例子具体运算一遍。 
       1.前两位必定为01。 
       2.计算第三位的时候,看第二位bnext值,为1,则把b1对应的a进行比较,不同,则第三位anext的值为1,因为一直比到最前一位,都没有发生比较相同的现象。 
       3.计算第四位的时候,看第三位anext值,为1,则把a1对应的a进行比较,相同,则第四位anext的值为第三位anext值加上1。为2。因为是在第三位实现了其next值对应的值与第三位的值相同。 
       4.计算第五位的时候,看第四位anext值,为2,则把a2对应的b进行比较,不同,则再将b对应的next1对应的a与第四位的a进行比较,相同,则第五位的next值为第二位bnext值加上1,为2。因为是在第二位实现了其next值对应的值与第四位的值相同。 
       5.计算第六位的时候,看第五位bnext值,为2,则把b2对应的b进行比较,相同,则第六位cnext值为第五位bnext值加上1,为3,因为是在第五位实现了其next值对应的值与第五位相同。 
       6.计算第七位的时候,看第六位cnext值,为3,则把c3对应的a进行比较,不同,则再把第3anext1对应的a与第六位c比较,仍然不同,则第七位的next值为1。 
       7.计算第八位的时候,看第七位anext值,为1,则把a1对应的a进行比较,相同,则第八位cnext值为第七位anext值加上1,为2,因为是在第七位和实现了其next值对应的值与第七位相同。 
       在计算nextval之前要先弄明白,nextval是为了弥补next函数在某些情况下的缺陷而产生的,例如主串为“aaabaaaab”、模式串为“aaaab”那么,比较的时候就会发生一些浪费的情况:比较到主串以及模式串的第四位时,发现其值并不相等,据我们观察,我们可以直接从主串的第五位开始与模式串进行比较,而事实上,却进行了几次多余的比较。使用nextval可以去除那些不必要的比较次数。 
       nextval数组值有两种方法,一种是不依赖next数组值直接用观察法求得,一种方法是根据next数组值进行推理,两种方法均可使用,视更喜欢哪种方法而定。 
       我们使用例子“aaaab”来考查第一种方法。 
       1.试想,在进行模式匹配的过程中,将模式串“aaaab”与主串进行匹配的时候,如果第一位就没有吻合,即第一位就不是a,那么不用比较了,赶快挪到主串的下一位继续与模式串的第一位进行比较吧,这时,模式串并没有发生偏移,那么,模式串第一位anextval值为0。 
       2.如果在匹配过程中,到第二位才发生不匹配现象,那么主串的第一位必定是a,而第二位必定不为a,既然知道第二位一定不为a,那么主串的第一、二两位就没有再进行比较的必要,直接跳到第三位来与模式串的第一位进行比较吧,同样,模式串也没有发生偏移,第二位的nextval值仍然为0。 
       3.第三位、第四位类似2的过程,均为0。 
       4.如果在匹配过程中,直到第五位才发生不匹配现象,那么主串的第一位到第四位必定为a,并且第五位必定不为b,可是第五位仍然有可能等于a。如果万一第五位为a,那么既然前面四位均为a,所以,只要第六位为b,第一个字符串就匹配成功了。所以,现在的情况下,就是看第五位究竟是不是a了。所以发生了下面的比较: 123456aaaa**aaaabaaaab 
       前面的三个a都不需要进行比较,只要确定主串中不等于b的那个位是否为a,即可以进行如下的比较:如果为a,则继续比较主串后面一位是否为b;如果不为a,则此次比较结束,继续将模式串的第一位去与主串的下一位进行比较。由此看来,在模式串的第五位上,进行的比较偏移了4位(不进行偏移,直接比较下一位为0),故第五位bnextval值为4。 
       我们可以利用第一个例子“abaabcac”对这种方法进行验证。 
       anextval值为0,因为如果主串的第一位不是a,那么没有再比较下去的必要,直接比较主串的第二位是否为a。如果比较到主串的第二位才发生错误,则主串第一位肯定为a,第二位肯定不为b,此时不能直接跳到第三位进行比较,因为第二位还可能是a,所以对主串的第二位再进行一次比较,偏移了1位,故模式串第二位的nextval值为1。以此类推,nextval值分别为:01021302。其中第六位的nextval之所以为3,是因为,如果主串比较到第六位才发生不匹配现象,那么主串的前五位必定为“abaab”且第六位必定不是“c”,但第六位如果为“a”的话,那么我们就可以从模式串的第四位继续比较下去。所以,这次比较为: 1234 5678910 11 12abaab*******abaabcac 
       而不是: 1234 5678910 11 12abaab*******abaabca 
       因为前两位ab已经确定了,所以不需要再进行比较了。所以模式串第六位的nextval值为这次比较的偏移量3。 
       再来看nextval数组值的第二种方法。 
模式串abaabcac next01122312nextval01021302 
       1.第一位的nextval值必定为0,第二位如果于第一位相同则为0,如果不同则为1。 
       2.第三位的next值为1,那么将第三位和第一位进行比较,均为a,相同,则,第三位的nextval值为0。 
       3.第四位的next值为2,那么将第四位和第二位进行比较,不同,则第四位的nextval值为其next值,为2。 
       4.第五位的next值为2,那么将第五位和第二位进行比较,相同,第二位的next值为1,则继续将第二位与第一位进行比较,不同,则第五位的nextval值为第二位的next值,为1。 
       5.第六位的next值为3,那么将第六位和第三位进行比较,不同,则第六位的nextval值为其next值,为3。 
       6.第七位的next值为1,那么将第七位和第一位进行比较,相同,则第七位的nextval值为0。 
       7.第八位的next值为2,那么将第八位和第二位进行比较,不同,则第八位的nextval值为其next值,为2。 
       “aaaab”内进行验证。 模式串aaaabnext01234nextval00004

<think>嗯,我现在要理解KMP算法中的nextval数组匹配过程图解。首先,我得回忆一下KMP算法的基本概念。KMP算法是用于字符串匹配的,它通过利用已经部分匹配的信息来避免重复检查字符,从而提高效率。关键点在于当出现不匹配时,如何移动模式串,这就需要用到next数组或者nextval数组。不过用户问的是nextval,所以我需要集中在这个部分。 首先,我需要明确next数组和nextval数组的区别。next数组存储的是模式串中每个位置之前的子串的最长相同前缀后缀的长度,这样当发生不匹配时,模式串可以跳过这些已经匹配的部分。而nextval数组是对next数组的优化,进一步减少不必要的比较。例如,当模式串中的某个字符与它的前缀字符相同时,可能可以跳过更多的位置,这时候nextval就会调整next数组中的值。 接下来,我需要思考如何构造nextval数组。假设模式串为P,长度为m。构造nextval数组的过程通常是在构造next数组的基础上进行的。对于每个位置j,如果P[j]等于P[next[j]],那么nextval[j]就等于nextval[next[j]],否则就是next[j]。这样可以避免重复比较相同的字符,从而优化移动的步数。 然后,用户需要的是匹配全过程的图解。这意味着我需要通过一个具体的例子来展示整个匹配过程,包括模式串和主串的每个字符比较步骤,以及如何利用nextval数组进行跳转。 例如,假设主串S是"ABABABACABA",模式串P是"ABABAC",那么首先需要构造P的nextval数组。让我先手动计算这个模式串的next和nextval数组。 模式串P的索引从0开始: 索引:0 1 2 3 4 5 字符:A B A B A C 首先计算next数组: - next[0] = -1(初始值) - next[1] = 0,因为前面没有相同的前缀后缀。 - next[2]:看前两位"AB",最长前后缀长度为0,所以next[2]=0? 等等,可能这里需要更仔细的计算。正确的next数组构造方法是,对于每个位置j,找最大的k(k<j)使得P[0..k-1] == P[j-k..j-1]。 实际计算: j=0: next[0] = -1 j=1: 前面只有"A",没有前缀后缀,所以next[1]=0 j=2: 子串"AB",最长前后缀长度0,next[2]=0 j=3: 子串"ABA",最长前后缀是"A",长度1,所以next[3]=1(对应索引0) j=4: 子串"ABAB",最长前后缀是"AB",长度2,所以next[4]=2(索引0和1) j=5: 子串"ABABA",最长前后缀是"ABA",长度3,所以next[5]=3 所以next数组为:[-1, 0, 0, 1, 2, 3] 接下来计算nextval数组。规则是,对于每个j,如果P[j] == P[next[j]],则nextval[j] = nextval[next[j]],否则nextval[j] = next[j]。 开始计算: j=0: nextval[0] = -1(因为next[0]=-1,没有字符可比较) j=1: next[1]=0,比较P[1]=B和P[0]=A,不等,所以nextval[1]=next[1]=0 j=2: next[2]=0,比较P[2]=A和P[0]=A,相等,所以nextval[2]=nextval[0]=-1 j=3: next[3]=1,比较P[3]=B和P[1]=B,相等,所以nextval[3]=nextval[1]=0 j=4: next[4]=2,比较P[4]=A和P[2]=A,相等,所以nextval[4]=nextval[2]=-1 j=5: next[5]=3,比较P[5]=C和P[3]=B,不等,所以nextval[5]=next[5]=3 所以nextval数组为:[-1, 0, -1, 0, -1, 3] 现在,假设主串S是"ABABABACABA",模式串P是"ABABAC",使用nextval数组进行匹配。 接下来需要一步步模拟匹配过程,记录每一步i(主串指针)和j(模式串指针)的位置,以及发生不匹配时如何调整j。 初始i=0,j=0: 1. S[0]=A 与 P[0]=A 匹配,i=1,j=1 2. S[1]=B 与 P[1]=B 匹配,i=2,j=2 3. S[2]=A 与 P[2]=A 匹配,i=3,j=3 4. S[3]=B 与 P[3]=B 匹配,i=4,j=4 5. S[4]=A 与 P[4]=A 匹配,i=5,j=5 6. S[5]=B 与 P[5]=C 不匹配。此时j=5,查nextval[5]=3,所以j=3。现在比较S[5]和P[3]=B,是否匹配? 主串S的i=5对应的是B,而模式串现在是P[3]=B,所以匹配。继续: 7. i=5+1=6,j=3+1=4 8. S[6]=A 与 P[4]=A 匹配,i=7,j=5 9. S[7]=C 与 P[5]=C 匹配,i=8,j=6。此时j=6超过模式串长度,匹配成功,起始位置是i-j=8-6=2? 这里可能哪里出错了,因为模式串长度是6(索引0到5),所以当j=6时应该已经结束,起始位置是i-j=8-5=3?或者可能需要重新计算。 或者可能在步骤6之后,当j=5不匹配时,j变成nextval[5]=3,此时i保持5不变,然后比较S[5]与P[3],即B和B,匹配,所以j增加到4,i增加到6。然后继续比较S[6]=A和P[4]=A,匹配,i=7,j=5。再比较S[7]=C和P[5]=C,匹配,i=8,j=6。此时j=6超过模式串长度,说明匹配成功,起始位置是i-j=8-5=3?或者i-j=8-6=2? 这里可能需要更仔细的步骤记录。 或者可能在步骤6中,当j=5不匹配时,j=nextval[5]=3,然后比较S[5]和P[3],即B和B,匹配,所以j++变为4,i++变为6。现在比较S[6]=A和P[4]=A,匹配,j=5,i=7。再比较S[7]=C和P[5]=C,匹配,j=6,i=8。此时j=6等于模式串长度(假设模式串长度为6,索引0-5),所以匹配成功,起始位置是i - j =8-6=2?或者i - 模式串长度=8-6=2?是的,起始位置为i - 模式串长度=8-6=2。所以主串中的位置2到7是匹配的? 但主串是"ABABABACABA",假设S的索引从0开始: 0:A,1:B,2:A,3:B,4:A,5:B,6:A,7:C,8:A,9:B,10:A 模式串匹配到位置i=8,j=6,说明模式串全部匹配完成,所以起始位置是8 -5(因为模式串长度6,索引0-5,所以长度是6)即8-6=2。因此,模式串出现在主串的位置2到7(包括6个字符?因为模式串长度6,所以应该是2到7?不过主串的i=8时,已经处理到S[7],所以可能结束位置是i=8时,模式串从2到7的位置匹配。 这个例子可能需要调整,或者选择一个更清晰的例子,比如主串是"ABABABACABA",而模式串"ABABAC"出现在位置0到5吗?或者中间可能存在不匹配的情况。可能我之前的例子中的主串在i=5的位置是B,而模式串的第五位是C,导致不匹配,之后调整j到3,重新比较。 不过可能这个例子需要更详细的步骤图解,可能需要用表格形式展示每一步的i和j的变化,以及主串和模式串的对齐情况。 另外,需要注意在KMP算法中,当发生不匹配时,i指针不回溯,只有j指针根据next或nextval数组调整。所以,当在某个位置j发生不匹配时,j变为nextval[j],然后继续比较S[i]和P[j]。 例如,在上述例子中,当j=5时不匹配,j变为3,此时比较S[i=5]与P[j=3]。如果匹配,则i和j都加1,否则继续调整j。 所以,详细步骤可能如下: 初始i=0,j=0: - S[0] = A,P[0] = A → 匹配,i=1,j=1 i=1,j=1: - S[1] = B,P[1] = B → 匹配,i=2,j=2 i=2,j=2: - S[2] = A,P[2] = A → 匹配,i=3,j=3 i=3,j=3: - S[3] = B,P[3] = B → 匹配,i=4,j=4 i=4,j=4: - S[4] = A,P[4] = A → 匹配,i=5,j=5 i=5,j=5: - S[5] = B,P[5] = C → 不匹配。查nextval[5]=3 → j=3 现在比较S[i=5]和P[j=3]: - S[5]=B,P[3]=B → 匹配,i=6,j=4 i=6,j=4: - S[6]=A,P[4]=A → 匹配,i=7,j=5 i=7,j=5: - S[7]=C,P[5]=C → 匹配,i=8,j=6 此时j=6,超过模式串长度(0-5),匹配成功,起始位置为i-j=8-6=2。 所以主串中从位置2开始匹配成功。 整个过程图示可能需要用箭头或者对齐的方式显示主串和模式串的位置变化,以及每次j调整后的比较位置。例如: 主串:A B A B A B A C A B A 索引:0 1 2 3 4 5 6 7 8 9 10 模式串第一次匹配尝试: A B A B A C 0 1 2 3 4 5 匹配到索引5时失败,B vs C。 调整j=nextval[5]=3,模式串右移,使得P[3]对齐S[5]: 此时模式串的位置: A B A B A C 0 1 2 3 4 5 主串位置5对应模式串位置3,比较B与B,匹配,继续比较后续字符。 这样一步步展示每个步骤的对齐和比较,就能形成图解的效果。 不过用户可能需要更直观的图示,但作为文字描述,我需要用文字模拟这个过程,可能需要使用表格或者分步骤描述每个比较阶段。 总结一下,nextval数组的构造和匹配过程的关键点在于优化next数组,避免重复比较相同字符的情况,从而减少不必要的移动,提升匹配效率。通过具体的例子分步骤解释,可以更清楚地展示这一过程。</think>KMP算法通过`nextval`数组优化匹配过程,减少不必要的比较。以下通过示例分步解析匹配全过程: --- ### **1. 示例设定** - **主串** $S$:`ABABABACABA`(索引从0开始) - **模式串** $P$:`ABABAC`(索引从0开始) --- ### **2. 构造`nextval`数组** #### 步骤1:计算`next`数组 | 索引 | 0 | 1 | 2 | 3 | 4 | 5 | |------|---|---|---|---|---|---| | 字符 | A | B | A | B | A | C | | `next` | -1 | 0 | 0 | 1 | 2 | 3 | #### 步骤2:优化为`nextval`数组 - **规则**:若 $P[j] = P[\text{next}[j]]$,则 $\text{nextval}[j] = \text{nextval}[\text{next}[j]]$,否则 $\text{nextval}[j] = \text{next}[j]$。 | 索引 | 0 | 1 | 2 | 3 | 4 | 5 | |------|---|---|---|---|---|---| | `nextval` | -1 | 0 | -1 | 0 | -1 | 3 | --- ### **3. 匹配全过程图解** #### **初始状态** 主串指针 $i=0$,模式串指针 $j=0$: ``` 主串 S: A B A B A B A C A B A 模式串 P: A B A B A C ^ (j=0) ``` --- #### **步骤1:$i=0, j=0$** - $S[0]=A$ vs $P[0]=A$,**匹配**,$i=1$, $j=1$。 ``` 主串 S: A B A B A B A C A B A 模式串 P: A B A B A C ^ (j=1) ``` --- #### **步骤2:$i=1, j=1$** - $S[1]=B$ vs $P[1]=B$,**匹配**,$i=2$, $j=2$。 ``` 主串 S: A B A B A B A C A B A 模式串 P: A B A B A C ^ (j=2) ``` --- #### **步骤3:$i=2, j=2$** - $S[2]=A$ vs $P[2]=A$,**匹配**,$i=3$, $j=3$。 ``` 主串 S: A B A B A B A C A B A 模式串 P: A B A B A C ^ (j=3) ``` --- #### **步骤4:$i=3, j=3$** - $S[3]=B$ vs $P[3]=B$,**匹配**,$i=4$, $j=4$。 ``` 主串 S: A B A B A B A C A B A 模式串 P: A B A B A C ^ (j=4) ``` --- #### **步骤5:$i=4, j=4$** - $S[4]=A$ vs $P[4]=A$,**匹配**,$i=5$, $j=5$。 ``` 主串 S: A B A B A B A C A B A 模式串 P: A B A B A C ^ (j=5) ``` --- #### **步骤6:$i=5, j=5$** - $S[5]=B$ vs $P[5]=C$,**不匹配**! 查`nextval[5]=3`,$j=3$,保持 $i=5$ 不变。 ``` 主串 S: A B A B A B A C A B A 模式串 P: A B A B A C ^ (j=3) ``` --- #### **步骤7:$i=5, j=3$** - $S[5]=B$ vs $P[3]=B$,**匹配**,$i=6$, $j=4$。 ``` 主串 S: A B A B A B A C A B A 模式串 P: A B A B A C ^ (j=4) ``` --- #### **步骤8:$i=6, j=4$** - $S[6]=A$ vs $P[4]=A$,**匹配**,$i=7$, $j=5$。 ``` 主串 S: A B A B A B A C A B A 模式串 P: A B A B A C ^ (j=5) ``` --- #### **步骤9:$i=7, j=5$** - $S[7]=C$ vs $P[5]=C$,**匹配**,$i=8$, $j=6$。 $j=6$ 超出模式串长度,匹配成功! --- ### **4. 匹配结果** 模式串 $P$ 在主串 $S$ 的起始位置为 $i-j=8-6=2$,即: ``` 主串 S: A B [A B A B A C] A B A ^ 匹配位置 ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值