引入

引入

SY 和 WYX 在看毛片。(几 毛 钱买到的动作 片,毛 片)
WYX 突然想回味一个片段,但是只记得台词里面有一句挺长的 “

****

∗∗∗∗ ”,于是,他们找到剧本,想看 “

****

∗∗∗∗ ”在剧本中出现了几次,分别是在什么地方。
他们遇到了麻烦,这样的剧本随便就是数百万单词,数千万字母,而且 “

****

∗∗∗∗ ”长度也有上千万。
为了解决这个问题,SY 发明了一个 O(N) 的字符串匹配算法,以这次的目的命名,就叫 KMP(看毛片) 算法。
但是他们不知道,前人已经发明此算法:

KMP 算法是一种改进的字符串匹配算法,由 D.E.Knuth,J.H.Morris 和 V.R.Pratt 提出的,因此人们称它为克努特—莫里斯—普拉特操作,简称 KMP 算法。
——摘自百度百科

气愤的 SY 只好继续看毛片,并顺便拿了 NOIP2020提高组 CQ前十

K

M

P

KMP算法讲解

KMP算法讲解

引入里面讲的很形象了,

K

M

P

KMP

KMP 算法是用来解决字符串匹配问题的,
问题原型就是在一个大字符串

S

1

S1

S1 里找一个小字符串

S

2

S2

S2 出现了多少次。

暴力怎么做的不用我说了吧,咱们直接进入正题。

K

M

P

KMP

KMP 算法由两个子任务组成,

S

2

S2

S2 每个前缀的最长

b

o

r

d

e

r

border

border

S

1

S1

S1 中匹配

S

2

S2

S2

Subtask 1 求 border(求 next )

b

o

r

d

e

r

border

border 是 “边界” 的意思,字符串内既是前缀又是后缀(而不等于原串)的一个子串,形象地叫它为该字符串的

b

o

r

d

e

r

border

border.

例:

  1. abcab d abcab
  2. SY is such a SY

上面两个字符串中加粗的地方就是该字符串的最长

b

o

r

d

e

r

border

border,字符串的

b

o

r

d

e

r

border

border 并不唯一,比如第一个字符串就还有另一个

b

o

r

d

e

r

border

border :“ ab ”,但是不是最长的。

根据这个定义,我们可以想想怎么线性地求

S

2

S2

S2 每一个前缀的最长

b

o

r

d

e

r

border

border 的长度。

K

M

P

KMP

KMP 算法中,我们定义

S

S

S 第

i

i

i 个前缀的最长

b

o

r

d

e

r

border

border 的长度为

n

e

x

t

S

[

i

]

next_S[i]

nextS​[i] (为什么叫 “next”,笔者也很好奇 )

从字符串前端算起,很明显,由于

n

e

x

t

[

i

]

<

i

next[i]<i

next[i]<i,所以

n

e

x

t

[

0

]

=

n

e

x

t

[

1

]

=

0

next[0]=next[1]=0

next[0]=next[1]=0. 1

然后往后算,设当前算到的位置为

i

i

i ,

首先,如果

S

[

i

]

=

=

S

[

n

e

x

t

[

i

1

]

+

1

]

S[i]==S[next[i-1]+1]

S[i]==S[next[i−1]+1] ,那么

n

e

x

t

[

i

]

=

n

e

x

t

[

i

1

]

+

1

next[i]=next[i-1]+1

next[i]=next[i−1]+1 ,而且这是

n

e

x

t

[

i

]

next[i]

next[i] 最好的情况,可以直接完事,去求

i

+

1

i+1

i+1 了,因为如果

n

e

x

t

[

i

]

>

n

e

x

t

[

i

1

]

+

1

next[i] > next[i-1]+1

next[i]>next[i−1]+1 的话,

n

e

x

t

[

i

1

]

next[i-1]

next[i−1] 肯定可以等于

n

e

x

t

[

i

]

1

next[i]-1

next[i]−1.

(next[i] : #### # ####

\rightarrow

→ next[i-1] : #### # ###(#))

那么否则就得找

i

1

i-1

i−1 的次大的

b

o

r

d

e

r

border

border ,以此类推。由于次大的

b

o

r

d

e

r

border

border 肯定满足是最大的 border 的前缀且后缀,因为:

  1. b

    o

    r

    d

    e

    r

    border

    border 对应最大

    b

    o

    r

    d

    e

    r

    border

    border 前缀部分的前缀:AAAA B AAAA

  2. b

    o

    r

    d

    e

    r

    border

    border 对应最大

    b

    o

    r

    d

    e

    r

    border

    border 后缀部分的后缀:AAAA B AAAA

  3. 最大

    b

    o

    r

    d

    e

    r

    border

    border 前缀部分和后缀部分显然相同:AAAA B AAAA

于是,可以充分证明,若当前的

b

o

r

d

e

r

border

border 大小为

x

x

x ,则次大的

b

o

r

d

e

r

border

border 为

n

e

x

t

[

x

]

next[x]

next[x] (前缀部分的

n

e

x

t

next

next)。

好,我们就可以处理出

S

S

S 每一个位置的

n

e

x

t

next

next 了。

那为什么它是线性的呢?我们可以隐约地意识到,每个位置的

n

e

x

t

next

next 没有向左扩展的过程,只有向右扩展,

由于

n

e

x

t

[

i

]

n

e

x

t

[

i

1

]

+

1

next[i] ≤ next[i-1]+1

next[i]≤next[i−1]+1 ,所以整个计算过程中,

i

n

e

x

t

[

i

]

“ i-next[i] ”

“i−next[i]” 这个量就从来没下降过,而且除了一开始判断

S

[

i

]

=

=

S

[

n

e

x

t

[

i

1

]

+

1

]

“S[i]==S[next[i-1]+1]”

“S[i]==S[next[i−1]+1]” 可能使该量不变以外,找次大

b

o

r

d

e

r

border

border 的操作每次一定会使

i

n

e

x

t

[

i

]

“ i-next[i] ”

“i−next[i]”变大,因此它是线性的。

模板

  1. void INIT(char *ss,int *nxt,int n) {
  2. nxt[0] = nxt[1] = 0;
  3. for(int i = 2;i <= n;i ++) {
  4. int nm = nxt[i-1]; nxt[i] = 0;
  5. while(nm && ss[nm+1] != ss[i]) nm = nxt[nm];
  6. if(ss[nm+1] == ss[i]) nxt[i] = nm+1;
  7. }
  8. return ;
  9. }

Subtask 2 字符串匹配

K

M

P

KMP

KMP 算法实际上是通过求出

S

1

S1

S1 每一个位置

i

i

i 向前延伸出最长的一段,满足是

S

2

S2

S2 的前缀,如果该段长度

=

l

e

n

g

t

h

S

2

= length_{S2}

=lengthS2​ ,那么

[

i

l

e

n

g

t

h

S

2

+

1
  

,
  

i

]

[i-length_{S2}+1\;,\;i]

[i−lengthS2​+1,i] 就是

S

2

S2

S2 的一个出现位置,也就是说

K

M

P

KMP

KMP 是间接地解决了这个问题,这表明着该算法的功能可以更强大。

怎么做呢

仿照着求

n

e

x

t

next

next 的推导,我们来求这个……不妨定义它为

F

F

F 吧,设

F

[

i

]

F[i]

F[i] 为位置

i

i

i 向前延伸出最长的一段,满足是

S

2

S2

S2 的前缀的长度。

从左到右依次计算吧,首先

F

[

0

]

=

0

F[0]=0

F[0]=0. 1

接下来对于过程中的

i

i

i ,如果

S

1

[

i

]

=

=

S

2

[

F

[

i

1

]

+

1

]

S1[i]==S2[F[i-1]+1]

S1[i]==S2[F[i−1]+1] 那么

F

[

i

]

F[i]

F[i] 直接等于

F

[

i

1

]

+

1

F[i-1]+1

F[i−1]+1 完事,否则找

n

e

x

t

[

F

[

i

1

]

]

next[F[i-1]]

next[F[i−1]],然后是

n

e

x

t

[

n

e

x

t

[

F

[

i

1

]

]

]

next[next[F[i-1]]]

next[next[F[i−1]]] ……直到后面那一位符号匹配。

这时候就会发现

n

e

x

t

[

]

next[]

next[] 有多么大的用处,因为其又是后缀又是前缀的性质,使得

i

i

i 可以正常地从

F

[

i

1

]

F[i-1]

F[i−1] 的一个

b

o

r

d

e

r

border

border 出转移过来。

它的复杂度和正确性都和

n

e

x

t

next

next 的证明类似,而且大多数人其实是第一部分看不懂而已,那就留给读者们一个思考空间吧(笔者要写扩展KMP了……)

模板

  1. //这里的代码特别灵活,每个题都不一样,笔者就不贴了

e

x

K

M

P

exKMP算法讲解

exKMP算法讲解

“加了 ‘ex’前缀的算法总会变得高端一些呢 ”

前面说了,

K

M

P

KMP

KMP 算法是求

S

1

S1

S1 每一个位置

i

i

i 向前延伸出最长的一段,满足是

S

2

S2

S2 的前缀的长度 ,而扩展

K

M

P

KMP

KMP 则是求

S

1

S1

S1 每一个位置

i

i

i 向后延伸出最长的一段,满足是

S

2

S2

S2 的前缀的长度 ,即,

S

1

S1

S1 每个后缀与

S

2

S2

S2 的最长公共前缀。

该算法也有两个子任务,

S

2

S2

S2 每个后缀和

S

2

S2

S2 本身的最长公共前缀长度

S

1

S1

S1 每个后缀中匹配

S

2

S2

S2(如上).

Subtask 1 ···

不妨设

e

x

[

i

]

ex[i]

ex[i] 为

S

2
   

i

S2\;\,i

S2i 开头的后缀和

S

2

S2

S2 本身的最长公共前缀长度,然后我们开始想怎么线性求它。

首先,

e

x

[

1

]

=

l

e

n

g

t

h

S

2

ex[1]=length_{S2}

ex[1]=lengthS2​,

e

x

[

2

]

ex[2]

ex[2] 可以暴力求出来。 1

接下来往后算,到了当前位置

i

i

i ,若

e

x

[

i

1

]

1

>

e

x

[

2

]

ex[i-1]-1 > ex[2]

ex[i−1]−1>ex[2] ,则

e

x

[

i

]

=

e

x

[

2

]

ex[i]=ex[2]

ex[i]=ex[2]。

因为它不能变得更长了,

如果变得更长的话,

会出问题的,

e

x

[

2

]

ex[2]

ex[2] 就可以变得更大了,因为

e

x

[

i

1

]

ex[i-1]

ex[i−1] 涵盖了更大范围的公共前缀,在

e

x

[

i

1

]

ex[i-1]

ex[i−1] 范围内都可以当作

S

2

S2

S2 开头考虑。

但是这并不能很好地衔接

i

+

1

i+1

i+1 ,因为这样一来直接进入

i

+

1

i+1

i+1 的话就要回退了,所以我们继续再判断是否

e

x

[

i

1

]

1

>

e

x

[

3

]

ex[i-1]-1 > ex[3]

ex[i−1]−1>ex[3](决定

e

x

[

i

+

1

]

ex[i+1]

ex[i+1] ) …… 最后起码会止步于

e

x

[

i

1

]

1

>

e

x

[

i

1

]

ex[i-1]-1>ex[i-1]

ex[i−1]−1>ex[i−1] 的判断(因为这肯定不成立),因此不存在访问了未计算部分的情况。

要是对于决定

e

x

[

i

+

x

2

]

ex[i+x-2]

ex[i+x−2] 的判断,

e

x

[

i

1

]

1

e

x

[

x

]

ex[i-1]-1 ≤ ex[x]

ex[i−1]−1≤ex[x] 呢?那就暴力从

i

+

e

x

[

i

1

]

2

i+ex[i-1]-2

i+ex[i−1]−2 再向右扩展就是了。这样一来就不会向左回退,只会向右扩展,保证了复杂度线性。

模板

  1. void INITex(char *ss,int *ex,int n) {
  2. ex[0] = 0;ex[1] = n;ex[2] = 0;
  3. int l = 0,r = 0;
  4. for(int i = 2;i <= n;i ++) { ex[i] = 0;
  5. if(i <= r) ex[i] = min(ex[i-l+1],r-i+1);
  6. while(i + ex[i] <= n && ss[i+ex[i]] == ss[ex[i]+1]) ex[i] ++;
  7. if(i + ex[i] - 1 > r) l = i,r = i + ex[i] - 1;
  8. }
  9. return ;
  10. }

Subtask 2 ···

也可以仿照

e

x

[

]

ex[]

ex[] 的计算。

不妨设

G

[

i

]

G[i]

G[i] 为

S

1

S1

S1 的第

i

i

i 位的后缀与

S

2

S2

S2 的最长公共前缀。

类似地,首先,

G

[

1

]

G[1]

G[1] 可以暴力跑出来。 1

然后,遍历到每一个

i

i

i ,若

G

[

i

1

]

1

>

e

x

[

2

]

G[i-1]-1 > ex[2]

G[i−1]−1>ex[2] (注意这里是

e

x

[

2

]

ex[2]

ex[2]),则

G

[

i

]

=

e

x

[

2

]

G[i]=ex[2]

G[i]=ex[2],然后继续判是否

G

[

i

1

]

1

>

e

x

[

3

]

G[i-1]-1>ex[3]

G[i−1]−1>ex[3] 来决定 G[i+1] …… 这里就不用担心未计算的问题,因为

e

x

[

]

ex[]

ex[] 肯定都处理完了。

直到找到一个不成立的,就从

i

+

G

[

i

1

]

2

i+G[i-1]-2

i+G[i−1]−2 再向右找,和

S

u

b

t

a

s

k
  

1

Subtask\;1

Subtask1 类似。

模板

  1. for(int i = 1;i <= n;i ++) {if(s2[i] == s1[i]) G[1] = i;else break;}
  2. int l = 1,r = G[1];
  3. for(int i = 2;i <= n;i ++) { G[i] = 0;
  4. if(i <= r) G[i] = min(ex[i - l + 1],r - i + 1);
  5. while(i + G[i] <= n && s1[i + G[i]] == s2[G[i] + 1]) G[i] ++;
  6. if(i + G[i] - 1 > r) l = i,r = i + G[i] - 1;
  7. }

  1. 1

    0

    _{特此说明一下,本文中的字符串下标从1开始,也就是说下标 0 表示空串}

    特此说明一下,本文中的字符串下标从1开始,也就是说下标0表示空串​

字符串KMP——用途广泛的字符串匹配算法 + 扩展KMP——特殊定义的字符串匹配的更多相关文章

  1. 51Nod - 1304 :字符串的相似度 (裸的扩展KMP)

    我们定义2个字符串的相似度等于两个串的相同前缀的长度.例如 "abc" 同 "abd" 的相似度为2,"aaa" 同 "aaab& ...

  2. 【怪物】KMP畸形变种——扩展KMP

    问题 参考51nod1304这道题: 很显然我们要求的是S的每个后缀与S的最长公共前缀的长度之和. 暴力 假设我们把next[i]表示为第i个后缀与S的最长公共前缀的长度. 现在我们想了:这个next ...

  3. 字符串匹配算法(KMP)

    字符串匹配运用很广泛,举个简单例子,我们每天登QQ时输入账号和密码,大家有没有想过账号和密码是怎样匹配的呢?登录需要多长时间和匹配算法的效率有直接的关系. 首先理解一下前缀和后缀的概念: 给出一个问题 ...

  4. 数据结构学习之字符串匹配算法(BF||KMP)

    数据结构学习之字符串匹配算法(BF||KMP) 0x1 实验目的 ​ 通过实验深入了解字符串常用的匹配算法(BF暴力匹配.KMP.优化KMP算法)思想. 0x2 实验要求 ​ 编写出BF暴力匹配.KM ...

  5. 字符串匹配算法之 kmp算法 (python版)

    字符串匹配算法之 kmp算法 (python版) 1.什么是KMP算法 KMP是三位大牛:D.E.Knuth.J.H.MorriT和V.R.Pratt同时发现的.其中第一位就是<计算机程序设计艺 ...

  6. hdu6153 扩展kmp求一个字符串的后缀在另一个字符串出现的次数。

    /** 题目:hdu6153 链接:http://acm.hdu.edu.cn/showproblem.php?pid=6153 题意:给定两个串,求其中一个串t的每个后缀在另一个串s中出现的次数乘以 ...

  7. kuangbin专题十六 KMP&&扩展KMP HDU2609 How many (最小字符串表示法)

    Give you n ( n < 10000) necklaces ,the length of necklace will not large than 100,tell me How man ...

  8. 【距离GDOI:137天】 扩展KMP...字符串QAQ

    上次和黄神两人一合计,干脆我学字符串他学图论,然后两人相互教...但以蒟蒻最近这状态来看,估计会到时候也教不了QAQ 扩展KMP大概是下面这个课件讲的这样 http://wenku.baidu.com ...

  9. 字符串匹配算法之kmp算法

    kmp算法是一种效率非常高的字符串匹配算法,是由Knuth,Morris,Pratt共同提出的模式匹配算法,所以简称KMP算法 算法思想 在一个字符串中查找另一个字符串时,会遇到如下图的情况 我们通常 ...

随机推荐

  1. model.apply(fn)或net.apply(fn)

    详情可参考:https://pytorch.org/docs/1.11/generated/torch.nn.Module.html?highlight=torch%20nn%20module%20a ...

  2. 2021.03.06【NOIP提高B组】模拟 总结

    T1 看起来十分复杂,打表后发现答案是 \(n*m\mod p\) 具体的证明... 原式的物理意义,就是从坐标原点(0,0),用每一种合法的斜率, 穿过坐标[1 ~ n , 1 ~ m]的方阵中的整 ...

  3. php 使用phpqrcode生成二维码并上传到OSS

    一般情况调用phpqrcode第三方插件 会把生成的二维码图片保存到服务器,不保存服务器也会以header头的形式输出到浏览器,(我们不允许把图片文件保存的liunx服务器,只能保存到阿里云OSS存储 ...

  4. 浅析DispatchProxy动态代理AOP

    浅析DispatchProxy动态代理AOP(代码源码) 最近学习了一段时间Java,了解到Java实现动态代理AOP主要分为两种方式JDK.CGLIB,我之前使用NET实现AOP切面编程,会用Fil ...

  5. 疫情在校学生之——用python对某校园热水服务app进行测试,实现自动免费用水(仅供参考)

    写在前面的过场话: 本文只是对某校园热水服务app做个测试,其实本人并没有做大坏事,并未传播相关技术,文章以下内容的敏感部分会打码,并且相关厂商已经正在进行漏洞修复,大家看看就好.文章后会提供&quo ...

  6. 使用 spring-security-oauth2 体验 OAuth 2.0 的四种授权模式

    目录 背景 相关代码 授权码模式 第一步 访问GET /oauth/authorize 第二步 访问POST /oauth/authorize 第三步 访问POST /oauth/token 简化模式 ...

  7. SpringBoot之MongoDB附件操作

    前言 近期自己针对附件上传进一步学习,为了弥足项目中文件上传的漏洞,保证文件上传功能的健壮性和可用性,现在我将自己在这一块的心得总结如下: 一.pom.xml依赖的引入 <dependency& ...

  8. RocketMQ之消费者启动与消费流程

    vivo 互联网服务器团队 - Li Kui 一.简介 1.1 RocketMQ 简介 RocketMQ是由阿里巴巴开源的分布式消息中间件,支持顺序消息.定时消息.自定义过滤器.负载均衡.pull/p ...

  9. linux服务配置IP或者说可以远程连接

    切换目录 cd /etc/sysconfig/network-scripts ls查看当前目录下的东西 找到ipcfg- 开头的,而且不是iocfg-lo,而上图就是那个ifcfg-ens33. 则进 ...

  10. Hippo4J v1.3.1 发布,增加 Netty 监控上报、SpringCloud Hystrix 线程池监控等特性

    文章首发在公众号(龙台的技术笔记),之后同步到博客园和个人网站:xiaomage.info Hippo4J v1.3.1 正式发布,本次发布增加了 Netty 上传动态线程池监控数据.适配 Hystr ...