C++接收字符串数组_KMP中的next和nextval数组值的求法

KMP算法是一种高效的字符串匹配算法,时间复杂度为O(m+n),空间复杂度为O(m)。该算法通过next数组记录模式串的前后缀公共长度,避免不必要的回溯。nextval是对next的优化,当不匹配时,能更快地找到正确位置。本文详细介绍了KMP算法的工作原理、next和nextval数组的计算,并通过实例展示了其应用。
摘要由CSDN通过智能技术生成

5f7ef86350484da9ccc527e20bd039e7.png

一、定义

KMP算法时间复杂度为O(m+n),空间复杂度为O(m)

KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt同时发现,因此人们称它为克努特——莫里斯——普拉特操作(简称KMP算法)。


二、图解原理

这种算法不太容易理解,网上有很多解释,但读起来都很费劲。

下面我将从计算机考研的角度来分析和梳理。(点赞的都考上研究生)

1.

89df9c973734d64af6f8603740b1f611.png

首先,字符串”BBC ABCDAB ABCDABCDABDE”的第一个字符与搜索词”ABCDABD”的第一个字符,进行比较。因为B与A不匹配,所以搜索词后移一位。

2.

5bcffaa0b1e46ccfb4ed9a2356ac9a8f.png

因为B与A不匹配,搜索词再往后移。

3.

2017bf8f8c0205b4d780602ffd9fbc30.png

就这样,直到字符串有一个字符,与搜索词的第一个字符相同为止。

4.

d354f0eb1c6cf327bacf621fa5e5eeb0.png

接着比较字符串和搜索词的下一个字符,还是相同。

5.

c69b9feca54a19d15cf95944d492de4d.png

直到字符串有一个字符,与搜索词对应的字符不相同为止。


当空格与D不匹配时,你其实知道前面六个字符是”ABCDAB”。KMP算法的想法是,设法利用这个已知信息,不要把”搜索位置”移回已经比较过的位置,继续把它向后移,这样就提高了效率。

6.

eeacee47ed0e04344bd0556d39312fbd.png

因为空格与C不匹配,搜索词还要继续往后移。

7.

2cb90990cbbe7b90ac8c1297953f1188.png

逐位比较,直到发现C与D不匹配。于是,继续将搜索词向后移动。

8.

2475fbc278a38260135f0d86c1ff6b94.png

逐位比较,直到搜索词的最后一位,发现完全匹配,于是搜索完成。


三、详解

目前求next数组有两种默认方式,

第一种:第一位默认-1,之后的每一位是它之前的字符串最长公共前缀后缀长度,得 -1 0 1 2

第二种:第一位默认0,第二位默认1,之后每一位之后的每一位是它之前的字符串最长公共前缀后缀长度+1

nextval是更新优化了next数组,以下讲解第二种

59941bc3512637156b67e2375b970ab3.png

1、KMP算法的核心思想

——就是回溯到存在”对称“的地方。

  • 注意,这里的“对称”不是指ABCCBA,而是指例如”ABCABC”中队前队尾都分别有1个”ABC”,又例如”ABCCCCCAB”中队前队尾都分别有一个”AB”。

2、看图说话

从第2部分图中可以分析得出,当扫描到模式串中某一位发现不匹配,总是回溯到在这一位之前的部分模式串存在重复的地方。

  • 例如2.5中找不到字母D(标号4),D之前串为”ABCDAB”,存在队前队尾重复”AB”(2个字符),因此退回到队首的“AB”后一位C(标号3)。
  • 例如2.6中找不到字母C(标号3),C之前串为”AB”,不存在重复的(0个字符),因此退回到队首最前面(标号1)。

所以,next(j)就是当模式串第j位不匹配时即将要退回到的字母标号。

  • 以上例子也可以看出,如果有2个字符重复,就退到第3位,如果有0个字符重复,就退到第1位。显然这是一个+1的关系。

3、next计算过程

不管第一位和第二位是什么,第一位next(1)=0,第二位next(2)=1,这是固定的。

当j=3时,P(j)=C,C之前有”AB”,不存在重复(0位),所以next(3)=0+1=1;
当j=4时,P(j)=D,C之前有”ABC”,不存在重复(0位),所以next(4)=0+1=1;
当j=5时,P(j)=A,C之前有”ABCD”,不存在重复(0位),所以next(5)=0+1=1;
当j=6时,P(j)=B,C之前有”ABCDA”,存在重复”A”(1位),所以next(6)=1+1=2;
当j=7时,P(j)=D,C之前有”ABCDAB”,存在重复”AB”(2位),所以next(7)=2+1=3;

4、nextval=对next的优化

观察第5位”A”,当它不匹配时,按照next行回溯到标号1也为字母A,这时再匹配A是徒劳的,因为已知A不匹配,所以就继续退回到标号1字母A的next(1)=0。为了直接点进行优化,就有了nextval行:

  • 只看前面有重复字母的几位就可以。

j=5,P(5)=A,next(5)=1,P(1)=A=P(5),所以nextval(5)=next(1)=0;
j=6,P(6)=B,next(6)=2,P(2)=B=P(6),所以nextval(6)=next(2)=1;
j=7,P(7)=D,next(7)=3,P(3)=C!=P(7),所以nextval(7)=next(7)=3;


结果:

30632255a6b54923221a6aa47607bf21.png

例题eg:字符串 ′ababaabab′ 的nextval为 ( )

解答

目前求next数组有两种默认方式,

第一种:第一位默认-1,之后的每一位是它之前的字符串最长公共前缀后缀长度

第二种:第一位默认0,第二位默认1,之后每一位之后的每一位是它之前的字符串最长公共前缀后缀长度+1,以下讲解第一种方法

i 0 1 2 3 4 5 6 7 8

s a b a b a a b a b

next[i] -1 0 0 1 2 3 1 2 3

先计算前缀next[i]的值:

next[i]的值主要是看s[i]之前的字符串中重复的子串长度。next[0] = -1,定值。

next[1]是看s[1]之前的字符串“a”中重复的子串长度为0,故next[1] = 0。

next[2]是看s[2]之前的字符串“ab”中重复的子串长度为0,故next[2] = 0。

next[3]是看s[3]之前的字符串"aba"中重复的子串长度,s[0]与s[2]重复,长度为1,故next[3] = 1。

next[4]是看s[4]之前的字符串"abab"中重复的子串长度,s[01]与s[23]重复,长度为2,故next[4] = 2。

next[5]是看s[5]之前的字符串"ababa"中重复的子串长度,s[012]与s[234]重复,长度为3,故next[5] = 3。

next[6]是看s[6]之前的字符串"ababaa"中重复的子串长度,s[0]与s[5]重复(因为多了一个a,无法找到长度为3的重复字符串,这只能是s[0]和s[5]重复),长度为1,故next[6] = 1。

同样的,求next[7]和next[8]分别为2和3。

接下来计算nextval[i]的值:

nextval[i]的求解需要比较s中next[i]所在位置的字符是否与s[i]的字符一致,如果一致则用s[next[i]]的nextval的值作为nextval[i],如果不一致,则用next[i]做为nextval[i]。

nextval[0] = -1,和next[0]的值一样。

nextval[1],比较s[next[1]] ?= s[1],next[1] = 0,s[0] = a,而s[1] = b,二者不一致,则nextval[1] = next[1] = 0。

nextval[2],比较s[next[2]] ?= s[2],next[2] = 0,s[0] = a,而s[2] = a,二者一致,则nextval[2] = nextval[s[next[2]]] = nextval[s[0]] = -1(严谨来看这么表述是有问题的,因为nextval[2]表示nextval数组中 第3个数值,而nextval[s[0]]表示的是s[0]对应的字母‘a’所对应的nextval值 -1,这里nextval[]的用法并不严谨,只是为了表述方便 )。

nextval[3],比较s[next[3]] ?= s[3],next[3] = 1,s[1] = b,而s[3] = b,二者一致,则nextval[3] = nextval[s[next[3]]] = nextval[s[1]] = 0。

nextval[4],比较s[next[4]] ?= s[4],next[4] = 2,s[2] = a,而s[4] = a,二者一致,则nextval[4] = nextval[s[next[4]]] = nextval[s[2]] = -1。

nextval[5],比较s[next[5]] ?= s[5],next[5] = 3,s[3] = b,而s[5] = a,二者不一致,则nextval[5] = next[5] = 3。

同样的求nextval[6],nextval[7],nextval[8]分别为 0 ,-1 , 0。

这里是nextval的下标从-1开始,如果从1开始,则其余各位均+1,nextval为0,1,0,1,0,4,1,0,1

参考资料

阮一峰的网络日志:字符串匹配的Boyer

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值