QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 2729|回复: 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算法—终于全部弄懂了2 e  n# Q8 t" r$ P# r
    简介
    ! u; r  k4 G/ o8 ?, `  KMP 算法是 D.E.Knuth、J,H,Morris 和 V.R.Pratt 三位神人共同提出的,称之为 Knuth-Morria-Pratt 算法,简称 KMP 算法。该算法相对于 Brute-Force(暴力)算法有比较大的改进,主要是消除了主串指针的回溯,从而使算法效率有了某种程度的提高。
    7 e& M0 z0 [5 ~9 r* q5 t4 x% x2 W/ k  ]0 M4 R; i+ ~& t
    ) h! W: q+ @: P; H3 U
    提取加速匹配的信息
    0 {. i/ r$ }& v' y4 X7 K  上面说道 KMP 算法主要是通过消除主串指针的回溯来提高匹配的效率的,那么,它是则呢样来消除回溯的呢?就是因为它提取并运用了加速匹配的信息!, b5 U: L* v& B# v0 \9 F' X
      这种信息就是对于每模式串 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 }。+ O; G( n0 ^0 r$ ]/ o2 v) S
        X. G+ i9 ]5 N+ d& F/ M! m
      加速信息,即数组 next 的提取是整个 KMP 算法中最核心的部分,弄懂了 next 的求解方法,也就弄懂了 KMP 算法的十之七八了,但是不巧的是这部分代码恰恰是最不容易弄懂的……
    ! ]1 w* [( B5 |- e- y2 R. {+ U/ R  . \0 s& p: ~# A) R- {1 h
    先上代码, J# J/ d' f9 n  x! O

    # y" A; z+ `5 O( x; z. k8 c
    * t7 W# k' Z1 Y- \  M$ W8 P
    void Getnext(int next[],String t)
    3 g9 r6 o$ M" M% K9 ?* g{: n$ {# ~2 u. L( @0 `4 @1 e
       int j=0,k=-1;1 T" d4 X6 `2 U' `5 t' W- q4 S
       next[0]=-1;( \! m0 d+ K% B) h1 G% Q  `; o- \" Z  M
       while(j<t.length-1)8 S* }; G4 Y! Y$ @
       {
    ; ]7 ]! g, q9 W: W      if(k == -1 || t[j] == t[k])' J7 f% \& L2 h) ]. {" y7 f4 ]$ t
          {. Q0 R& I( }0 C$ _6 k
             j++;k++;
    4 j7 S& E: L+ ?         next[j] = k;
    4 D. a. ]! w- F, @5 \$ H      }2 Z. E( k( b  q, x' h$ Z- z+ y
          else k = next[k];//此语句是这段代码最反人类的地方,如果你一下子就能看懂,那么请允许我称呼你一声大神!9 Y4 _( t9 l% N. }/ X
       }; |: U. N- T$ u5 [8 ?+ ~' I  {
    }
    % O/ G9 f# _) o# |6 E: c0 ]" w4 I' W6 b
    ok,下面咱们分三种情况来讲 next 的求解过程8 N& t' F" w7 S, A

    $ s$ a) |! x0 ~! `
    # ^" z# ?" H6 W7 w  J
    特殊情况4 N. G; D" {* j+ Q, v% |
    当 j 的值为 0 或 1 的时候,它们的 k 值都为 0,即 next[0] = 0、next[1] =0。但是为了后面 k 值计算的方便,我们将 next[0] 的值设置成 -1。0 ?" Q, i9 J, k  B

    2 x( Y1 `& R6 f

    7 L  q: @: J1 _, T2 a. {7 q当 t[j] == t[k] 的情况
    ) k; m: `2 J1 H; G- n( l) H举个栗子' x3 n( y' Z* R, n! P
    1111.png
    5 W  m% k, [; w8 B1 z+ k2 p; T+ ~' C观察上图可知,当 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。3 Z+ t5 f' [; |  M; o! c6 w
    ( D* X% I3 y* S
    % t7 T5 k4 n% N8 V7 M
    当t[j] != t[k] 的情况
    # G! j$ z& y. q' H7 R关于这种情况,在代码中的描述就是“简单”的一句 k = next[k];。我当时看了之后,感觉有点蒙,于是就去翻《数据结构教程》。但是这本书里,对于这行代码的解释只有三个字:k 回退…!于是我从“有点蒙”的状态升级到了“很蒙蔽”的状态,我心想,k 回退?我当然知道这是 k 退回,但是它为什么要会退到 next[k] 的位置?为什么不是回退到k-1???巴拉巴拉巴拉…此处省略一万字。
    / z( Z* j  k! E& t# C3 {- ?% M( Y
    ' `7 B( y( }1 \, U! F
    2 Z: b$ k0 T2 b  u# M8 V4 v
    我绞尽脑汁,仍是不得其解。于是我就去问度娘…
    # X" {$ h3 ~# D4 t+ I1 M: N在我看了众多博客之后,终于有了一种拨云见日的感觉,看下图" K! `. u# y" K* p7 I5 t% A, `
    2222.png ' j: F# _, s3 ^
       由第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。0 b- q, J9 m0 k1 N: ^* Z

    # u& Z' ]# s% X  @: e
    * K% X6 g3 Y+ u( n' I  H; ]
    至此,算是把求解数组 next 的算法弄清楚了(其实是,终于把 k = next[k] 弄懂了…)2 c' v! g9 @5 }/ z1 }

    ; w$ T0 l9 Y+ l) m0 P
    ( Q; o6 A$ Q7 ]& ^9 `  O
    因为这个算法神奇难解之处就在k=next[k]这一处的理解上,网上解析的非常之多,有的就是例证,举例子按代码走流程,走出结果了,跟肉眼看的一致,就认为解释了为什么k=next[k];很少有看到解释的非常清楚的,或者有,但我没有仔细和耐心看下去。我一般扫一眼,就大概知道这个解析是否能说的通。仔细想了三天,搞的千转百折,山重水复,一头雾气缭绕的。搞懂以后又觉得确实简单,但是绕人,烧脑。
    5 [0 Y% g; @# \' o7 m
    9 ~* J% E' \8 s
    ! w) Q# F6 b0 U* F
    再此特别感谢昵称为“sofu6”的博客园主,正是他的博客,让我这愚笨的脑袋瓜开窍了2 g  t5 v7 B; X, w

    ! D$ X# G8 x0 N# f, w% ~2 J5 s

    ' @3 r4 t$ X7 N8 b' w' j5 mKMP算法实现
    + G  I: j. M, c% t( I( `3 d9 t当你求出了 next 数组之后,KMP 算法就很轻易搞定了,下面我用三张图,让你明白 KMP 算法完成匹配的整个过程。  P/ [& o0 e2 x; w
    以目标串:s,指针为 i ;模式串:t 指针为 j ; 为例
    # O' G" E9 @. m5 x  d+ o 3333.png " n. @5 U9 ?7 W& Y
    上图表示:“si-j ~ si-1” == “t 0 ~ t j-1”,s i != t j(前面都相等,但比较到 t j 时发现不相等了)且next[j] == k。
    5 g3 _0 a) K2 _* ]0 s$ M7 W 4444.png
    # G" {$ e' E! f* e9 s5 c根据 next 数组的定义得知 “t k ~ t j-1” == “t 0 ~ t k-1”,所以 “t 0 ~ t k-1” == “si-k ~ si-1”- u8 K: g0 Y; q$ A3 Y! Y
    555.png
    ! B! G& ^6 |+ v$ b8 [; y将模式串右移,得到上图,这样就避免了目标穿的指针回溯。! `4 I- F3 a- A( S. B
    3 K3 d0 S) n' m: b+ _6 J) M
    / P0 M. J. J, f: z- {+ N
    都明了之后就可以手写 KMP 的代码了; D8 z5 f: z$ G0 A
    / N, t: q! a! p6 H& n

    6 F7 q3 ~" r" Oint KMP(String s,String t)
    4 ^9 z0 h" K8 G' F& H{5 @/ s- K; X0 m3 l" W: [. M& e
       int next[MaxSize],i=0;j=0;
    , p! ~1 y; M; O; I' ], K# l- a   Getnext(t,next);
    / j& N$ b' Z) i, h2 g* Z5 v/ J   while(i<s.length&&j<t.length)! x% t# n$ V& a% L
       {2 H# `) v( [; w  Z0 l3 o7 p
          if(j==-1 || s==t[j])' Q2 ~/ |/ m+ z% n; z# O: y. B
          {
    0 b! V2 q; c0 y9 J9 K5 _         i++;8 g. v5 g3 Q5 l9 t1 q
             j++;) x2 L& i+ t& M& E
          }
    , ?7 s' [3 H: m+ E7 o: K( P2 Q) F* M      else j=next[j];               //j回退。。。  J% {  Y8 K: z8 E' ?5 b2 F" R
       }. ]: G* [- @8 L' V
       if(j>=t.length)
    1 |. D0 ]% h8 J1 V# o6 _' u       return (i-t.length);         //匹配成功,返回子串的位置1 b2 i/ s4 t# v0 a. {, U: a# u
       else
    # Q. y( n+ @9 J$ F/ o7 H% }      return (-1);                  //没找到
    5 G6 |: ~+ w. y( {" P; p+ D}. C2 P2 |  f: b
    $ Q+ S- x; O+ d! Y/ b# ~
    改进后的 next 求解方法, ]( z% J. Z( L/ y
    先来看一下上面算法存在的缺陷:
    / b) T6 c8 Q7 n. h6 A+ V 6666.png
    2 J  Y# P0 d" r0 U5 m* j显然,当我们上边的算法得到的next数组应该是[ -1,0,0,1 ]
    1 y9 g: c& J, T8 M8 L6 s: z5 D# f9 L& t$ b, `
    0 P" p3 y4 p& W+ |( g
    所以下一步我们应该是把j移动到第1个元素咯:
    1 t  V3 z- s2 ^' _6 V$ \- M 7777.png ) k6 J% m/ y9 f% [6 z- ?. w3 h, n
    不难发现,这一步是完全没有意义的。因为后面的B已经不匹配了,那前面的B也一定是不匹配的,同样的情况其实还发生在第2个元素A上。+ L# Q! ?: D+ P$ @! r& ?

    0 N+ b3 p9 _+ ?1 u5 N
    + g7 I/ J- S, i0 _. e4 h
    显然,发生问题的原因在于t[j] == t[next[j]]。9 Z+ b9 U, W# }7 `1 A0 [# K+ @. N+ }
      r) f9 D- U0 }1 y

    : d- e. _5 X+ U: i6 n8 M! A- X所以我们需要谈价一个判断:
    ( v6 {% b* }( b0 E' y- j* T- a/ R6 M$ K1 o

    + l  Y' U+ w- Y  T+ o2 {void Getnext(int next[],String t)
    % \: [' l( R( f0 R9 C6 x% n{
    ( a; p7 b& }4 }1 U  S$ B   int j=0,k=-1;: Q& H2 ?, d0 I5 O" l2 T; A
       next[0]=-1;. R% A4 {5 M1 |' u8 I0 S- z: u
       while(j<t.length-1)
    - j# O3 K& U, p1 C6 b2 M+ _   {
    . u. y/ F$ Y2 Z: U+ v' C+ w      if(k == -1 || t[j] == t[k])
    ) ?+ h2 S: ?0 g6 @$ A      {
    % I  [6 {& m5 J" o6 F, ]. T7 e$ y         j++;k++;
    : H* Q" d# @9 G: ^, c) }, n9 E         if(t[j]==t[k])//当两个字符相同时,就跳过2 q9 B; f8 }. t7 I6 ^
                next[j] = next[k];5 w+ R3 q0 C, o1 s- U/ W# V( i# M* K
             else
    + c# W+ z# v& C$ B( C            next[j] = k;
    - X8 H, R9 I% l4 J      }
    $ ^' Z; w3 ~1 w1 B5 `/ C" ~% c6 w* E      else k = next[k];8 d2 J) g2 H# n, M
       }  i; Q3 V4 M- a+ G- M
    }+ B5 |7 J. a& x6 Y
    ————————————————
    ) B- @& x3 p; y! z, F# W  \版权声明:本文为CSDN博主「June·D」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。0 m! e# n* I4 h0 k+ G6 {9 B( w
    原文链接:https://blog.csdn.net/dark_cy/article/details/886987367 ?' |- j: K' G$ \% u+ f. w3 s0 r

    6 L* Q3 k) Q) a% G/ W0 m
    / [, ~* a, `) Q4 _- X; P0 ^
    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, 2026-6-17 17:21 , Processed in 0.442605 second(s), 59 queries .

    回顶部