KMP算法

回顾/本期梗概

        上期我们学习了离散化(空降链接),本期我们将学习KMP算法。

        KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt提出的,因此人们称它为克劳特——莫里斯——普拉特操作(简称KMP算法)。KMP算法的核心是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是通过一个next()函数实现,函数本身包含了模式的局部匹配信息。KMP算法的时间复杂度O(m+n)


1、KMP的原理 

        如:有上图所示的父串s和子串p,如果要匹配出子串p在父串s中是否出现过或者出现了几次。

        暴力的做法如图所示,第1次父串s从第1的字符开始和子串匹配,由第1次匹配可知,S[2]= 'B' 而t1='A',显然匹配不成功。第3次从s的第3个字符开始尝试和子串p匹配......

        如果s的长度为n,p 的长度为m,则暴力求解的时间复杂度是O(n*m)

        问题:是否直接跳过中间肯定失败的3次匹配呢?

        回答:答案是肯定的,因为第 1 次匹配,发现 s[ 8 ] != p[ 8 ] 时,观察 p 的前 7 个字符发现,p 有 3 个前缀子串和 3 个后缀子串完全匹配,可以利用这个性质,直接跳过中间的 3 次无效的匹配。

  


2、next 数组的计算方法

        (1)加快匹配的原理

        上述案例中,我们使用 i 来循环 s 主串,j 循环 p 子串,我们希望:s 主串的 [ i - j + 1,i ] 字符和 p 子串的 [ 1,j ] 字符完全一致,也就是 s 串以 s[ i ] 结尾的长度为 j 的子串和 p 字符串的前 j 个字符一致,显然样例可以分析出 i = 7、j = 7 时是能做到的,接下来检验 s[ i + 1 ] 和 p[ j + 1 ]的关系。

        由于 s[ 8 ] != p[ 8 ],此时 i 和 j 无法继续增加,要尝试继续匹配,必须减少 j,且为了提升效率,新的 j 越大越好。因为 p[ 1... 3] == p[ 5...6 ],因此新的 j 最大可以设置为 3,到下图所示的状态,重新检验 s[ i + 1 ] 和 p[ j + 1 ] 的关系。

        当发现 s[ i + 1 ] != p[ j + 1 ] 时,j 的最大可以回跳的值 == p 字符串 1... j 这 j 个字符前缀和后缀相等的最大长度。

        (2)next 数组的含义

        定义 next 数组的含义:next[ j ] 代表字符串 p 中前 j 个字符的最大相同前缀和后缀的值。特别注意:这个值不含本身,比如字符串“ ABABA ” 最大相同前缀和后缀的值是 3,而不是 5,因为任何字符串如果取自身,那么从前缀和后缀必定相等。因此 next[ 0 ] = next[ 1 ] = 0。

        (3)如何高效匹配

        当 s[ i + 1 ] != p[ j + 1 ],通过 j = next[ j ] 回跳,如果 s[ i + 1 ] == p[ j + 1 ],那么往后继续匹配。(注意:为了避免 next 和系统函数同名,一般 next 数组定义时可以使用缩写 ne )

        (4)如何高效求 next 数组

        类似字符串 s 和 p 匹配的过程,只是求 p 的 next 数组本质上是 p 自己和自己匹配的过程。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值