kmp算法拆解

kmp算法拆解

一、简介

KMP算法是一种高效的字符串匹配算法,由D.E. Knuth、J.H. Morris和V.R. Pratt共同提出。

二、KMP算法

1.基本概念

子串:子串是由一个字符串(也称为父串或主串)中任意个连续字符组成的子序列。

子序列:子序列是从原始序列中抽取一部分元素构成的新序列,这些元素的相对顺序保持不变,但不需要连续

二者关系示意图:

image

2.朴素匹配算法

案例:

image

2.1朴素匹配概念

前提:主串长度为n、模式串长度为m

**概念:**将主串中所有长度为m的子串与模式串对比,直到找到一个完全匹配的字串,或者所有子串都不匹配

2.2朴素匹配原理

2.2.1key1:在朴素算法中最多对比几个子串?

答案:n-m+1

最多->最坏的情况 :

  • 即匹配每次匹配到模式串的最后一个失败

  • 匹配到最后一个子串

S:aaac          aaac          aaac

T:ab                ab               ab

设初始指针都是1

image

显然找到最后一个子串的位置,即最多对比的子串数即可求

  1. 观察法:n-m+1

  2. 我们知道每次匹配失败模式串指针就会回溯至1,最差情况回溯至1,回溯坐标为m-(m-1)

父串也需要同样的回溯长度   m-1,回溯到n-(m-1)=n-m+1

key1结论:最多对比n-m+1个子串

2.2.2key2:通过key1,我们知道了模式串匹配失败的指针操作j=1,那么父串指针i呢?
  • 子串回溯最差j = 1

  • 父串回溯最差i = n-m+1  即匹配到最后一个子串

  • 如果是中间的子串呢?很简单 把i = n-m+1中的n和m换成指针位置即可i=i-j+1

  • 父串匹配失败需回溯再移动至下一位 即i = (i-j+1)+1 = i-j+2

key2结论:父串指针i需移动至i-j+2

2.2.3key3:代码实现

image

2.2.4key4:上述算法以及对应代码时间复杂度是多少?

结论:O(nm)

原因:按照最坏的情况,n-m+1个子串,每个子串匹配m次,

共匹配次数是:m(n-m+1)=mn-m²+m

实际情况中,父串长度n远大于模式串m,所以复杂度为nm,而不是m

3.kmp算法

3.1情景举例:

父串:googllgoogle

子串:google

3.1.1key5:此时我们如果按照朴素匹配,会怎么样?

父串:指针回溯i-j+1=1,向前移动

子串:指针回溯1

变成如下

父串:googllgoogle

子串:  google

很显然不对,如上,会进行无效回溯。

我们需要做的是,以上述为例,第6个元素发生匹配失败,则直接

父串:googllgoogle

子串:         google

key5结论:在绝大多数情况,朴素匹配会无意义指针回溯,所以需要优化。

思考:在日常生活中,我们会对例子中的匹配失败进行这么多遍的回溯吗?

3.2kmp算法

3.2.1key6:kmp算法思路引入

image

在3.1的例子中,模式串的第6位发生了不匹配,3.2的例子是在第5位发生了匹配,这里该如何做呢?和3.1中一样吗

image

正确做法:image

结合3.1中 j=6发生不匹配,则让j指针回到1

依次类推:当j=1、2、3。。。时发生不匹配,j的回溯分别是?

image

对于j的回溯,我们可以给出一个next数组,放入数据,在第k个字符不匹配时候,通过数组拿取指针的回调

key6结论:kmp的算法意在取消i无意义的回溯,并且给j指针的回溯指定了位置形成一个数组

next[模式串匹配失败位置]=j回溯的地方

3.3kmp算法核心之模式串指针回溯数组next

3.3.1key7:如何求next数组?

以google举例,在k处发生匹配失败,则把1~k-1看成一个串S

S串的前缀:包含第一个字符且不包含最后一个字符的子串

S串的后缀:包含最后一个字符且不包含第一个字符的子串

next[j]=最长相等前后缀长度+1

第1个字符发生匹配失败:j=0

第2个字符发生匹配失败:j=1

这两个是固定的

第3个字符发生匹配失败 前缀 g 后缀o 最长相等为0 所以 next[3]=0+1 =1

。。。

第5个字符发生匹配失败 前缀g 后缀g 最长相等为1 所以next[5]=1+1=2

key7结论:通过发生匹配失败的位置,并在这个位置之前的串中找最长相等的前后缀长度并+1,填充数组

3.3.2next和kmp代码实现

比较kmp算法和朴素匹配,发现两个不同点:

  1. i指针不需要回溯

  2. j指针的回溯遵循了next数组

image

KMP算法性能分析

image

短一点的模式串我们可以手算,但是实际应用肯定不会这么短,参考百度搜索,所以需要代码实现

在kmp算法中,有一个关键点,不管是在kmp还是在kmp求next数组中,总有一个指针不回溯,这就是优化的地方

时间复杂度为O(n+m)

3.4kmp针对next数组再优化

3.4.1key8:为什么需要再优化

aba**?**

ababa

根据3.3的内容,可知ababa的next数组是01123

示例中,在模式串的第4个发生匹配错误,按照常规kmp的next数组应该回溯到第2个,但是很显然,第2个也是b

无效匹配出现

结论:next数组回溯的坐标对应元素和匹配失败发生位置的元素相同,需要解决该无效匹配

3.4.2key9如何优化next数组:nextval数组

aba**?**    aba**?**       aba**?**

ababa      ababa      a****b…

上述流程中,如果按照之前的next数组会进行如上图的无效匹配,如何有效(如黄色底色示)

创建一个基于next数组的nextval数组image

改进关键点:如果我们按照next回溯,在下标3处 a 发生错误,会跳到S[next[3]]=a,又跟a比,浪费,需要优化

就是有点绕,但是不难:

  1. 目标:防止回溯的模式串的值和发生匹配错的模式串值一样

  2. 对模式串遍历,目的就是防止T[next[i]]==T[i]

  3. 针对T[next[i]]==T[i],创建一个新数组nextval[i]=nextval[next[i]]

  4. 如果没有T[next[i]]==T[i],就还是正常复刻 nextval[i] = next[i]

3.4.3nextval改进代码实现

image

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值