QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 2675|回复: 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算法—终于全部弄懂了
      k. |0 ]' E. N( Q简介& y& D/ _& r: Z0 X6 c9 \
      KMP 算法是 D.E.Knuth、J,H,Morris 和 V.R.Pratt 三位神人共同提出的,称之为 Knuth-Morria-Pratt 算法,简称 KMP 算法。该算法相对于 Brute-Force(暴力)算法有比较大的改进,主要是消除了主串指针的回溯,从而使算法效率有了某种程度的提高。! w- m  h9 Z- b! p6 I, v
    4 m5 I5 R+ e8 t* \* G

    5 R8 K0 s. S; p0 a  c( S$ A. I提取加速匹配的信息* |! i, `1 h0 D) p) r
      上面说道 KMP 算法主要是通过消除主串指针的回溯来提高匹配的效率的,那么,它是则呢样来消除回溯的呢?就是因为它提取并运用了加速匹配的信息!
      m: M, A; O, n; D+ p7 K& J; O  这种信息就是对于每模式串 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 }。
    6 P0 B6 c+ k" c6 [, V& ?; O1 v  
    6 T: F1 l  L: N- M  M4 j  加速信息,即数组 next 的提取是整个 KMP 算法中最核心的部分,弄懂了 next 的求解方法,也就弄懂了 KMP 算法的十之七八了,但是不巧的是这部分代码恰恰是最不容易弄懂的……
    # A- n' T- _! s) O; s# r/ L  
    * U) j" W' E+ s; t先上代码
    ) i/ V+ I( `/ k0 S- L  f) R, Z1 f% s7 V/ D) h% p6 Q# Y$ c1 P1 w0 b9 h4 D

    2 H* q/ f! b) t6 H- A2 Fvoid Getnext(int next[],String t)
    " s- m9 @8 K, U" O* U+ K- X! B{4 y, p" v- B) w4 f2 P: Q- J
       int j=0,k=-1;
    5 Y3 V0 A+ S$ i$ a& F   next[0]=-1;1 V5 P* a6 n+ ]2 `4 D% z& U5 i1 ~
       while(j<t.length-1)
    5 p$ C: o0 J# Q  `% _# x" v, c) Y   {% C2 _  t* ]3 C5 m! V
          if(k == -1 || t[j] == t[k])$ M" ~6 p! a  {
          {
    : @; B, t+ r! u         j++;k++;+ S" K7 O# s% D. Q& b7 ]+ N
             next[j] = k;
    & J9 |8 n+ i6 E) T      }( k/ h; a' L0 z4 @8 U1 W
          else k = next[k];//此语句是这段代码最反人类的地方,如果你一下子就能看懂,那么请允许我称呼你一声大神!) f$ e6 e: S4 R
       }9 Y: D- T6 t5 P. Y4 U
    }
    % Q& s3 T' M2 w( m6 [
    5 H+ p2 J3 a/ i7 J2 Vok,下面咱们分三种情况来讲 next 的求解过程' G6 M8 q! @- H8 r

    3 V4 @& d; d; o* h5 C

    & s  j# D9 v# m0 K特殊情况
    + L7 d  I& w0 V( l! _当 j 的值为 0 或 1 的时候,它们的 k 值都为 0,即 next[0] = 0、next[1] =0。但是为了后面 k 值计算的方便,我们将 next[0] 的值设置成 -1。
    $ {/ H- k( b# S7 A& [6 I, O' v# ]  X7 x! _5 v5 M
    + z) ?5 A- I( f0 |1 T1 F
    当 t[j] == t[k] 的情况
    . f+ F9 T2 {/ E" y3 k( j举个栗子* }, j8 F" g; A1 w& ^
    1111.png ; Z' {. j8 r- U: j2 I0 H4 g/ s6 U
    观察上图可知,当 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。( l, d% U4 C: ?. o/ y
    1 J4 }: T5 M, d8 t& F
    , J  L# _, W$ Y8 \+ C: c9 L7 k
    当t[j] != t[k] 的情况3 _$ c) Q. `5 Y6 `: c
    关于这种情况,在代码中的描述就是“简单”的一句 k = next[k];。我当时看了之后,感觉有点蒙,于是就去翻《数据结构教程》。但是这本书里,对于这行代码的解释只有三个字:k 回退…!于是我从“有点蒙”的状态升级到了“很蒙蔽”的状态,我心想,k 回退?我当然知道这是 k 退回,但是它为什么要会退到 next[k] 的位置?为什么不是回退到k-1???巴拉巴拉巴拉…此处省略一万字。6 L+ p% j( C2 f5 F2 E+ J2 D1 r" {2 R7 x4 e

    ; q1 g4 y" U& d! ]

    ! U$ D4 [, \4 v' W6 C我绞尽脑汁,仍是不得其解。于是我就去问度娘…
    3 V; `& ?$ x  W4 Z) N0 T/ x% S0 s  Z在我看了众多博客之后,终于有了一种拨云见日的感觉,看下图
    . y2 h% h' {& }/ `! S  q0 P 2222.png ! q& O+ |6 s9 f+ b
       由第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。8 e$ K5 p* ?* X" ~8 O- s2 y( p

    " m1 b5 K" e, m

    ! I, S% g# x: t4 c8 y至此,算是把求解数组 next 的算法弄清楚了(其实是,终于把 k = next[k] 弄懂了…); ~1 I9 o8 V- i, o, e
    ( J6 I) v: i' D3 K6 Q; ], N, s7 _
    8 g3 x8 O9 k1 I7 L
    因为这个算法神奇难解之处就在k=next[k]这一处的理解上,网上解析的非常之多,有的就是例证,举例子按代码走流程,走出结果了,跟肉眼看的一致,就认为解释了为什么k=next[k];很少有看到解释的非常清楚的,或者有,但我没有仔细和耐心看下去。我一般扫一眼,就大概知道这个解析是否能说的通。仔细想了三天,搞的千转百折,山重水复,一头雾气缭绕的。搞懂以后又觉得确实简单,但是绕人,烧脑。6 w; f3 V# Q; v
    % x4 p% a* Y3 a. z/ D& i
    9 D% p5 d6 A0 I& u" C6 t; ~$ e
    再此特别感谢昵称为“sofu6”的博客园主,正是他的博客,让我这愚笨的脑袋瓜开窍了
    / D8 _2 `0 G: Z8 N- ^  l8 v) _' ?5 O+ P: p+ v

    . y0 f7 }1 g) ^  LKMP算法实现. |* G# W  S3 j9 Z3 F0 X
    当你求出了 next 数组之后,KMP 算法就很轻易搞定了,下面我用三张图,让你明白 KMP 算法完成匹配的整个过程。
    - A3 G3 H, L# X! S5 @以目标串:s,指针为 i ;模式串:t 指针为 j ; 为例
    . _! o. d% q$ I* W2 P 3333.png
    ! T- @4 q( Y# ~5 x* t7 v上图表示:“si-j ~ si-1” == “t 0 ~ t j-1”,s i != t j(前面都相等,但比较到 t j 时发现不相等了)且next[j] == k。
    7 t/ J! W. D0 l! m 4444.png ; u: O; J4 d; i% D& @' g1 O; s6 M
    根据 next 数组的定义得知 “t k ~ t j-1” == “t 0 ~ t k-1”,所以 “t 0 ~ t k-1” == “si-k ~ si-1”- x( Y6 W: K: R/ D4 _& T
    555.png 3 N& T& F2 n( {0 K% e5 ?; |! h
    将模式串右移,得到上图,这样就避免了目标穿的指针回溯。/ G( U* [( l9 S

    % w6 D5 q2 ~8 U$ J2 ~

      A9 y& j5 x" k1 m都明了之后就可以手写 KMP 的代码了
    3 v) d' J' b3 j- Q
    ' |+ _+ V7 y8 t) x2 |5 h. d1 e# g

    8 |8 e; C6 W; P( R  J) Kint KMP(String s,String t)1 Z% u) A- A! }
    {
    ( }+ G9 P5 h) G- \: n) x   int next[MaxSize],i=0;j=0;
    9 n# k/ R+ ?' J+ N, Q1 t   Getnext(t,next);
    0 [3 t! @! ]( \   while(i<s.length&&j<t.length)8 m2 m& B; j7 U
       {
    1 I- s! J8 L0 G5 ^3 H# i      if(j==-1 || s==t[j])
    4 ~  i) o* ?" b+ L: {3 }      {- Z8 w; @, ~& M; c  y5 t
             i++;
    6 X  A* Z& o  ?3 F         j++;
    0 o  [. x$ u9 {3 K/ Z1 _      }( g* K& y; H% t% `4 K8 S7 H/ T
          else j=next[j];               //j回退。。。
    $ C* I0 [# @9 ?$ O4 X6 T, J5 @   }' I, |& J3 A! I* G/ S
       if(j>=t.length)
    8 y% O( G0 u3 ?- ]0 ^       return (i-t.length);         //匹配成功,返回子串的位置
    * w5 S6 _& t2 L4 p6 m4 D+ g9 [   else& k* u1 V* y1 P! |8 h4 @/ r
          return (-1);                  //没找到# {' E( L0 S2 a$ F4 Q
    }0 B  t) ?5 b; E4 N8 x) |
    7 i4 R/ w: U$ u0 w
    改进后的 next 求解方法
    0 Y. J1 N/ W3 c$ m先来看一下上面算法存在的缺陷:
    : w* {0 Q8 l; j2 {" s1 U* ~ 6666.png 6 R0 w' m: N9 m' ?7 u: r' K! O
    显然,当我们上边的算法得到的next数组应该是[ -1,0,0,1 ]
    2 u7 b% L9 o; a, z% ]! z5 e2 ]8 d( C$ M) x2 z# q: G( _  n2 t
    $ u! S* L7 M0 u- B9 H% I
    所以下一步我们应该是把j移动到第1个元素咯:
    3 n# \* L" Q2 `3 e  T8 |- z 7777.png / G7 a1 j5 t' R" [7 b
    不难发现,这一步是完全没有意义的。因为后面的B已经不匹配了,那前面的B也一定是不匹配的,同样的情况其实还发生在第2个元素A上。
    4 G) K/ d$ i: x. I
    0 Q1 t' i' k/ v3 Z+ g- e3 L+ V
    2 C- W: {: [, q' R
    显然,发生问题的原因在于t[j] == t[next[j]]。* B1 G; y9 t% ]0 Z$ \1 @+ @
    : C; P7 l/ u: K' z- m. `
    7 {% w4 X& R8 [4 p( {
    所以我们需要谈价一个判断:
    ! S. p5 K' u2 l8 I2 D4 u
    ' `: z! r# }" i: L

    3 p9 Q7 R* {2 r4 i0 i4 i; h! \9 [void Getnext(int next[],String t)
    & M8 \. W8 f5 D) C2 N{6 v2 d# q: B2 B0 d  A
       int j=0,k=-1;
    5 x: [$ ^4 M" E% u' s) R   next[0]=-1;: h0 Z; |. o; f
       while(j<t.length-1)/ [: b* P0 m* C5 G6 _- m
       {' Q3 P0 y+ o+ _# b5 x) W
          if(k == -1 || t[j] == t[k])+ B8 {" v1 U- ?  z2 h/ x
          {" Z6 c- a/ U+ Y5 j7 D" m. T
             j++;k++;. o- Z7 K6 n8 X3 _7 V1 }4 K
             if(t[j]==t[k])//当两个字符相同时,就跳过
    % }& {; J  e' t1 R4 ~            next[j] = next[k];  ^6 A" }' y! v# v% H, A
             else
    5 c' |4 x8 R* d( Y4 D2 z# t            next[j] = k;
    8 f$ L& t* |9 L5 U      }
    8 x& o9 \) ]' b- v8 Y) w/ V: ?      else k = next[k];; E( e) B4 P& ^9 U3 Y2 `
       }
    , t/ q2 p, e$ s: P9 X# J}
    ; ^& {4 U2 M* S( h. @6 r8 H————————————————( G, t. {: M3 _. s
    版权声明:本文为CSDN博主「June·D」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    3 N4 r5 m2 w3 A! \原文链接:https://blog.csdn.net/dark_cy/article/details/88698736
    6 G' z( M  {" D. }. m2 [# k& ]- E# m5 ?
    3 r  }; O; G% W" Z: R: N( B: t
    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-4-13 07:18 , Processed in 0.534413 second(s), 58 queries .

    回顶部