QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 2491|回复: 1
打印 上一主题 下一主题

KMP算法—终于全部弄懂了

[复制链接]
字体大小: 正常 放大
杨利霞        

5273

主题

82

听众

17万

积分

  • TA的每日心情
    开心
    2021-8-11 17:59
  • 签到天数: 17 天

    [LV.4]偶尔看看III

    网络挑战赛参赛者

    网络挑战赛参赛者

    自我介绍
    本人女,毕业于内蒙古科技大学,担任文职专业,毕业专业英语。

    群组2018美赛大象算法课程

    群组2018美赛护航培训课程

    群组2019年 数学中国站长建

    群组2019年数据分析师课程

    群组2018年大象老师国赛优

    跳转到指定楼层
    1#
    发表于 2021-8-10 16:52 |只看该作者 |倒序浏览
    |招呼Ta 关注Ta
    KMP算法—终于全部弄懂了1 Q4 h/ ^4 p4 N4 ~3 U; g
    简介
    $ z) U, _- u$ E7 h$ k7 j$ J  KMP 算法是 D.E.Knuth、J,H,Morris 和 V.R.Pratt 三位神人共同提出的,称之为 Knuth-Morria-Pratt 算法,简称 KMP 算法。该算法相对于 Brute-Force(暴力)算法有比较大的改进,主要是消除了主串指针的回溯,从而使算法效率有了某种程度的提高。
    & V# ?) M7 f3 i9 F2 H& N0 H/ U- M9 _! N0 o) H* V& A$ [; @% _
    ' R% F* D% ]( y0 u, U
    提取加速匹配的信息5 @! e8 ?  x$ @7 d; b" S6 R
      上面说道 KMP 算法主要是通过消除主串指针的回溯来提高匹配的效率的,那么,它是则呢样来消除回溯的呢?就是因为它提取并运用了加速匹配的信息!; E6 q8 d$ V# F- r
      这种信息就是对于每模式串 t 的每个元素 t j,都存在一个实数 k ,使得模式串 t 开头的 k 个字符(t 0 t 1…t k-1)依次与 t j 前面的 k(t j-k t j-k+1…t j-1,这里第一个字符 t j-k 最多从 t 1 开始,所以 k < j)个字符相同。如果这样的 k 有多个,则取最大的一个。模式串 t 中每个位置 j 的字符都有这种信息,采用 next 数组表示,即 next[ j ]=MAX{ k }。
      Q5 k& z* S1 Z+ c* I  . C" c+ d+ k" L! C2 |; j9 v, z
      加速信息,即数组 next 的提取是整个 KMP 算法中最核心的部分,弄懂了 next 的求解方法,也就弄懂了 KMP 算法的十之七八了,但是不巧的是这部分代码恰恰是最不容易弄懂的……/ D( \+ g$ Z; b- p6 n, ^- r6 i) O& `
      " z9 x1 B/ M, q' \6 t8 a
    先上代码
    ( H8 o& G% i. ]$ f& Q. x& ^) S
    ! c' T' h- U, T% g
    6 S% D7 c* E, A( Q/ |8 o1 u
    void Getnext(int next[],String t)
    " d6 e% u9 x6 W" t$ ?) P{
    ; |6 i  }  A9 Z' G+ l9 T   int j=0,k=-1;
    # ?* m# n3 q. f2 ~& q) G+ v   next[0]=-1;  s% r% Z8 Y/ t/ `4 {
       while(j<t.length-1)
    ' {* l0 f1 ~; {' a   {: c! y3 x$ x  t8 x0 S
          if(k == -1 || t[j] == t[k])
    1 s6 u! K! V9 h& N      {( Y6 C+ Y8 g/ n8 h9 w1 `1 V$ ^# I
             j++;k++;
    ( y7 }: ]2 F4 x6 r0 s         next[j] = k;( D- ~9 J7 \8 R9 ^0 G7 ?
          }+ B. F1 q( @" B% u4 J
          else k = next[k];//此语句是这段代码最反人类的地方,如果你一下子就能看懂,那么请允许我称呼你一声大神!
    0 w- }/ V- D3 `: ~/ K   }
    / S. @0 y7 G% {: X$ I3 f}7 ]0 U( W2 \1 `. I
    - J! v8 y* n6 u# \
    ok,下面咱们分三种情况来讲 next 的求解过程! l4 N% N( g+ A# [

    9 C3 ]2 @  O; [& g* z

    * o( F' T. n' a0 p5 M( }特殊情况8 Z' [2 w$ p2 A6 w2 F5 I: J
    当 j 的值为 0 或 1 的时候,它们的 k 值都为 0,即 next[0] = 0、next[1] =0。但是为了后面 k 值计算的方便,我们将 next[0] 的值设置成 -1。) A% r: U/ x! t4 r

      d- G, J  P6 e' d& _
    0 E2 P: a' }3 @8 M# e
    当 t[j] == t[k] 的情况
    7 T% e. p# ?% [; f& K* q举个栗子
    6 R3 R" C+ l/ B% D6 F4 r 1111.png
    6 \, m( d  J& v% f# ?观察上图可知,当 t[j] == t[k] 时,必然有"t[0]…t[k-1]" == " t[j-k]…t[j-1]",此时的 k 即是相同子串的长度。因为有"t[0]…t[k-1]" == " t[j-k]…t[j-1]",且 t[j] == t[k],则有"t[0]…t[k]" == " t[j-k]…t[j]",这样也就得出了next[j+1]=k+1。/ B' T( y' w' a2 ^

    . e* k; p- o/ d, S

    6 K6 P3 j- R5 b# E( q) k- s& V当t[j] != t[k] 的情况5 d7 d8 m/ l6 ]
    关于这种情况,在代码中的描述就是“简单”的一句 k = next[k];。我当时看了之后,感觉有点蒙,于是就去翻《数据结构教程》。但是这本书里,对于这行代码的解释只有三个字:k 回退…!于是我从“有点蒙”的状态升级到了“很蒙蔽”的状态,我心想,k 回退?我当然知道这是 k 退回,但是它为什么要会退到 next[k] 的位置?为什么不是回退到k-1???巴拉巴拉巴拉…此处省略一万字。
    ! h% ~$ `: P+ k2 f
      j* e4 D- L& a
    / o7 v( v; U- a  `1 s' d
    我绞尽脑汁,仍是不得其解。于是我就去问度娘…
    5 W- b4 f+ e' D: a7 B% k在我看了众多博客之后,终于有了一种拨云见日的感觉,看下图" b, u  t" }1 e* o0 t$ a6 Y. l
    2222.png
    ' H& L5 S( \! d5 T8 O& Q7 V   由第2中情况可知,当 t[j] == t[k] 时,t[j+1] 的最大子串的长度为 k,即 next[j+1] = k+1。但是此时t[j] != t[k] 了,所以就有 next[j+1] < k,那么求 next[j+1] 就等同于求 t[j] 往前小于 k 个的字符(包括t[j],看上图蓝色框框)与 t[k] 前面的字符(绿色框框)的最长重合串,即 t[j-k+1] ~ t[j] 与 t[0] ~ t[k-1] 的最长重合串(这里所说“最长重合串”实不严谨,但你知道是符合 k 的子串就行…),那么就相当于求 next[k](只不过 t[k] 变成了 t[j],但是 next[k] 的值与 t[k] 无关)!!!。所以才有了这句 k = next[k],如果新的一轮循环(这时 k = next[k] ,j 不变)中 t[j] 依然不等于 t[k] ,则说明倒数第二大 t[0~next[k]-1] 也不行,那么 k 会继续被 next[k] 赋值(这就是所谓的 k 回退…),直到找到符合重合的子串或者 k == -1。7 W1 a- A) y& a( U! @, l* C

    2 l5 g$ J/ K9 a2 a, H

    4 Q/ c7 ]& h, x7 m至此,算是把求解数组 next 的算法弄清楚了(其实是,终于把 k = next[k] 弄懂了…). ~* M' I$ F! g) i, }: \6 T" i

    $ b0 o  X" i. f9 W- E3 u) P

    8 V3 s0 v& A; t' ~因为这个算法神奇难解之处就在k=next[k]这一处的理解上,网上解析的非常之多,有的就是例证,举例子按代码走流程,走出结果了,跟肉眼看的一致,就认为解释了为什么k=next[k];很少有看到解释的非常清楚的,或者有,但我没有仔细和耐心看下去。我一般扫一眼,就大概知道这个解析是否能说的通。仔细想了三天,搞的千转百折,山重水复,一头雾气缭绕的。搞懂以后又觉得确实简单,但是绕人,烧脑。
    * E! ~5 |- C% k% l3 s3 L) ~' n2 k' s9 b/ D3 v0 N

    ; w0 H3 l; G. Q# f再此特别感谢昵称为“sofu6”的博客园主,正是他的博客,让我这愚笨的脑袋瓜开窍了
    , O9 j- a$ I1 Z( K5 _) s
      h/ `0 P4 T4 n7 b3 d% t* U. |: t5 p

    3 W1 q8 o- f! Y) u. jKMP算法实现
    + F8 f$ B7 o) A- \& N, e当你求出了 next 数组之后,KMP 算法就很轻易搞定了,下面我用三张图,让你明白 KMP 算法完成匹配的整个过程。
    ( r* P; h  N  f" ^+ f5 W* |以目标串:s,指针为 i ;模式串:t 指针为 j ; 为例
    % V& u" Z, v8 V' _1 U8 r- L 3333.png % k9 n( V8 Z( }; r, Z0 S
    上图表示:“si-j ~ si-1” == “t 0 ~ t j-1”,s i != t j(前面都相等,但比较到 t j 时发现不相等了)且next[j] == k。
    ; i' x/ P2 v2 _* [ 4444.png
    & r  {" V0 P& l+ l7 h, v" r' Y  k根据 next 数组的定义得知 “t k ~ t j-1” == “t 0 ~ t k-1”,所以 “t 0 ~ t k-1” == “si-k ~ si-1”
    6 ]8 B8 w1 p" A0 G4 F: k 555.png 2 \9 J' U) H8 J$ c( r3 O& l
    将模式串右移,得到上图,这样就避免了目标穿的指针回溯。
      J# ]% ^% h( l/ Q3 Z- K/ l" i$ p; R; Q' z! J

    ' N9 ~' W1 t( J. Z  O  B, {7 x都明了之后就可以手写 KMP 的代码了- m2 I( m% K+ d, ~
    $ i, d7 Z% O7 X- [' M

    9 H/ b+ g8 A& A0 F/ P2 M7 Dint KMP(String s,String t)! R6 m. ], ?: S9 I# A, \7 i
    {# h, P7 s* c- Y  q' T, t; t
       int next[MaxSize],i=0;j=0;
    0 b4 o! }. M; L! b; Y; ~% X   Getnext(t,next);
    8 T  m$ |% M, n# E, W2 T" c$ @   while(i<s.length&&j<t.length)( u2 _" k+ g0 G
       {/ E7 `8 k8 r8 t
          if(j==-1 || s==t[j])0 K: y5 l( T9 l& p5 ]+ ^) D! x
          {4 S3 c5 B& F8 `- {, v8 A
             i++;
    7 l  D$ S" ^7 ^8 ^! f         j++;
      N4 H+ `* u; h: M' ]- x! ^      }
    & ]! h' o* L. g3 f/ v7 ]' `      else j=next[j];               //j回退。。。
    : D# o$ K5 ?% w* B2 T) A+ Z6 a   }
    $ \9 a6 p; p4 s5 g) T- o( Q% S+ E; q* ^   if(j>=t.length)
    . C) ?, j7 v3 G( Y       return (i-t.length);         //匹配成功,返回子串的位置8 _# k# j! y: k" m, t
       else2 N5 }8 T1 }- @# ~) D' e3 s( E' i" h& M
          return (-1);                  //没找到$ e% ]" e" u$ k. x& I
    }
    + s+ `) m1 I" p  {
    & G9 P# a$ y% c, R改进后的 next 求解方法- W2 h* j& K& l% h" r
    先来看一下上面算法存在的缺陷:
      @$ r9 c& P% i: ]! ~8 y- ]1 ^8 a 6666.png , V6 A/ A/ W0 A
    显然,当我们上边的算法得到的next数组应该是[ -1,0,0,1 ]
    , L: L8 C- |, [, Q  {9 @% u# h# _4 Q& V' T" a) ?5 O# l
    " ^$ M* U# q  L- S* }
    所以下一步我们应该是把j移动到第1个元素咯:
    3 Z: g. m6 d; B; I5 M$ U4 y! _ 7777.png 8 N) d3 `8 S: ^' E, \5 n
    不难发现,这一步是完全没有意义的。因为后面的B已经不匹配了,那前面的B也一定是不匹配的,同样的情况其实还发生在第2个元素A上。
    ) L0 K+ p( b6 z5 Y9 V) Y7 u2 Q. F) N# K8 ]1 Y) B& d
    * J9 r1 J/ H7 g: D
    显然,发生问题的原因在于t[j] == t[next[j]]。
    0 v, [8 `) U. g( s! U. U. J0 y5 `3 b+ ]
    / |& K. @! a+ P) g
    所以我们需要谈价一个判断:
    " S" x7 k7 m! O5 M6 A7 Y) `- M' e* A6 i+ ^6 Z, a+ H

    1 j8 V$ Q# v# [( c4 Nvoid Getnext(int next[],String t)5 s) O/ F* u: u$ D3 w% }0 ?  K
    {7 n! ?& u, E8 P, e3 J
       int j=0,k=-1;
    5 k; V& Z. A' @9 i( b   next[0]=-1;
    : ~$ g" k  _. w9 Q3 h* ~5 ~/ @* a   while(j<t.length-1)
    3 s7 w5 A( e7 G: Z0 @  h   {
    " K3 B% P# i2 v( S- X" g      if(k == -1 || t[j] == t[k])
    6 @0 T/ A# C2 h1 n* Q) \      {# p  x8 W4 y+ U( q
             j++;k++;
    8 X+ {: M3 ]* w0 V/ d7 V/ q         if(t[j]==t[k])//当两个字符相同时,就跳过/ C* v! u" `( S. y; ?
                next[j] = next[k];
    , _9 ~' H. `0 ~  l! T1 X6 ~1 z- q         else0 ~8 D) m" X; ~# [+ x
                next[j] = k;
    ' ^( ?/ f1 U$ i/ _      }
    ! V- u% L  H* e4 k: a      else k = next[k];
    * U9 X8 Q' B, h' Z: p) e   }
    . y8 R2 d3 p+ v" j* c}( }7 }1 g* A0 P8 N1 J1 p
    ————————————————/ M+ m( _! U/ J$ ^# O
    版权声明:本文为CSDN博主「June·D」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。$ r. ~# H$ f) K4 G% u
    原文链接:https://blog.csdn.net/dark_cy/article/details/88698736
    3 ?5 G0 X, F. [, ^7 D% w1 S: ~; Y+ F- c6 z" o# x3 J; g- P$ D* a

    0 w& p' I5 }6 x2 q' e3 y
    zan
    转播转播0 分享淘帖0 分享分享0 收藏收藏0 支持支持0 反对反对0 微信微信

    0

    主题

    10

    听众

    299

    积分

    升级  99.5%

  • TA的每日心情
    开心
    2023-10-14 10:28
  • 签到天数: 28 天

    [LV.4]偶尔看看III

    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 注册地址

    qq
    收缩
    • 电话咨询

    • 04714969085
    fastpost

    关于我们| 联系我们| 诚征英才| 对外合作| 产品服务| QQ

    手机版|Archiver| |繁體中文 手机客户端  

    蒙公网安备 15010502000194号

    Powered by Discuz! X2.5   © 2001-2013 数学建模网-数学中国 ( 蒙ICP备14002410号-3 蒙BBS备-0002号 )     论坛法律顾问:王兆丰

    GMT+8, 2025-8-23 08:09 , Processed in 0.640866 second(s), 58 queries .

    回顶部