QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 2523|回复: 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算法—终于全部弄懂了
    ! h9 f( y' P# ^- \5 P& d+ R简介1 J! j1 m3 \( l* R2 W$ A  B
      KMP 算法是 D.E.Knuth、J,H,Morris 和 V.R.Pratt 三位神人共同提出的,称之为 Knuth-Morria-Pratt 算法,简称 KMP 算法。该算法相对于 Brute-Force(暴力)算法有比较大的改进,主要是消除了主串指针的回溯,从而使算法效率有了某种程度的提高。
    $ J* K+ A5 v! h; Y0 ?
    ! j5 t+ W2 T2 u3 ~9 L$ s, c
    3 h  {4 T- P; o, {3 p. `% G
    提取加速匹配的信息  {8 f! j6 Y" _- ?, Y; h/ V
      上面说道 KMP 算法主要是通过消除主串指针的回溯来提高匹配的效率的,那么,它是则呢样来消除回溯的呢?就是因为它提取并运用了加速匹配的信息!* F/ J* Y$ \+ I: c# v
      这种信息就是对于每模式串 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 }。2 A. z8 g) ^) |! R
      4 _" @# M( x: n
      加速信息,即数组 next 的提取是整个 KMP 算法中最核心的部分,弄懂了 next 的求解方法,也就弄懂了 KMP 算法的十之七八了,但是不巧的是这部分代码恰恰是最不容易弄懂的……  I8 ^2 w  C/ X1 {  T0 V: b" S
      
    . q: A  j8 j3 t: b, G先上代码7 K3 T$ v! U0 I  e9 C' P5 c3 q
    4 `* O, O' p+ S
    $ a$ `( ?1 n; ]
    void Getnext(int next[],String t), F6 `" o8 J$ p8 ]; K
    {
    + t# v! M5 D! _3 a# N3 v   int j=0,k=-1;5 q6 b1 [, J" N
       next[0]=-1;
    : W5 @* X0 `+ {! D0 D" E   while(j<t.length-1)8 O5 V) r" k% S% V( j) l% [6 y
       {- Q' F2 T: N7 R4 d! S" p
          if(k == -1 || t[j] == t[k]): C) i6 D9 Z/ d1 e0 {. P
          {% }/ R- c' T  P
             j++;k++;
    # h1 _, I$ `$ f# ?: F         next[j] = k;
    . z" U& ?8 L& E5 M, t      }4 d+ s  {9 |7 O! \1 D8 {( @+ x2 I
          else k = next[k];//此语句是这段代码最反人类的地方,如果你一下子就能看懂,那么请允许我称呼你一声大神!2 U: `' O3 B! ~0 Q' H5 {1 `
       }
    - r( ^* \( H8 }8 E}
    0 n! N! a  q$ E' l% `: L; \: o4 o9 a, v8 M: @/ J
    ok,下面咱们分三种情况来讲 next 的求解过程# R( K5 G1 ~/ v2 J& @& l2 d
    % B2 H* J* F% U* W
    + l1 @  t1 @) j
    特殊情况* d; d$ N/ |& i. w6 N1 g
    当 j 的值为 0 或 1 的时候,它们的 k 值都为 0,即 next[0] = 0、next[1] =0。但是为了后面 k 值计算的方便,我们将 next[0] 的值设置成 -1。9 \: J$ u& @% ^: S# z' O% k5 p

    ; X% V0 E3 ]* ]! m# [! J# d
    * h2 w4 c9 S! j! M
    当 t[j] == t[k] 的情况& G' e# n9 l! d3 t/ a
    举个栗子, [/ s5 R9 `5 W, H2 T' h
    1111.png $ H/ b: M* K/ h$ E9 s. L9 r1 O
    观察上图可知,当 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。
    / X7 s+ d! U9 M6 B  V8 S: C3 Q9 s# a) J  u0 K
    ' @, L. W: H% q  B+ I
    当t[j] != t[k] 的情况
    8 D" E: k' L. t5 J6 Q" o8 ~关于这种情况,在代码中的描述就是“简单”的一句 k = next[k];。我当时看了之后,感觉有点蒙,于是就去翻《数据结构教程》。但是这本书里,对于这行代码的解释只有三个字:k 回退…!于是我从“有点蒙”的状态升级到了“很蒙蔽”的状态,我心想,k 回退?我当然知道这是 k 退回,但是它为什么要会退到 next[k] 的位置?为什么不是回退到k-1???巴拉巴拉巴拉…此处省略一万字。
    - r2 i1 i$ S7 C6 v" N) O3 X( W
    4 k9 L) p2 a2 t3 ?6 f. i' v
      ~9 y" z6 ~1 Q/ u
    我绞尽脑汁,仍是不得其解。于是我就去问度娘…$ \3 p2 b2 u0 k4 v4 l& f
    在我看了众多博客之后,终于有了一种拨云见日的感觉,看下图4 a$ K/ ^6 Z) @# l/ P% m9 E
    2222.png   ]% U# L7 `* Q
       由第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。; h# @9 {( |; R

    1 |) T. {  r. f) p3 h# A

    ; q3 e, {% y2 ?& d" d- P) [至此,算是把求解数组 next 的算法弄清楚了(其实是,终于把 k = next[k] 弄懂了…)
    2 |: T! t% }* E! n" G
    4 O3 r; ?3 r; _# T7 [  j

    ! L7 b5 {% u) r9 M/ C* R因为这个算法神奇难解之处就在k=next[k]这一处的理解上,网上解析的非常之多,有的就是例证,举例子按代码走流程,走出结果了,跟肉眼看的一致,就认为解释了为什么k=next[k];很少有看到解释的非常清楚的,或者有,但我没有仔细和耐心看下去。我一般扫一眼,就大概知道这个解析是否能说的通。仔细想了三天,搞的千转百折,山重水复,一头雾气缭绕的。搞懂以后又觉得确实简单,但是绕人,烧脑。
    , m+ u/ x; e& f* P1 Z7 R8 z( ~% d; E' A5 p& W2 J. n/ e8 r

    & s7 M& y/ W' V( J) [3 E5 U0 J7 r再此特别感谢昵称为“sofu6”的博客园主,正是他的博客,让我这愚笨的脑袋瓜开窍了
    & q# I( O& E$ @1 b  Q  M+ Q  j( ?7 o1 `2 F* f; ?6 D& |

    7 D  C" B/ D5 _0 R( x& M: pKMP算法实现
    7 }2 b) I9 P5 l9 @. A% C1 Z当你求出了 next 数组之后,KMP 算法就很轻易搞定了,下面我用三张图,让你明白 KMP 算法完成匹配的整个过程。
    1 Z7 K% |; }0 U5 v/ Z6 Z以目标串:s,指针为 i ;模式串:t 指针为 j ; 为例1 i+ [$ [0 }. W  r
    3333.png 2 e! d0 L8 c" b% j( ^# d: a6 k: u
    上图表示:“si-j ~ si-1” == “t 0 ~ t j-1”,s i != t j(前面都相等,但比较到 t j 时发现不相等了)且next[j] == k。3 w  k$ m) K" J, \. f
    4444.png
    ( ]. k9 g1 A9 X4 v& b8 i* P根据 next 数组的定义得知 “t k ~ t j-1” == “t 0 ~ t k-1”,所以 “t 0 ~ t k-1” == “si-k ~ si-1”2 U- {! U& Z7 E7 g
    555.png ) t4 n8 Q1 K0 h2 s
    将模式串右移,得到上图,这样就避免了目标穿的指针回溯。
    2 l1 [5 R0 Q" j  f+ i3 U/ }3 R- l
    + ^( T- _6 y# \
    2 K) p" b" J( l5 z
    都明了之后就可以手写 KMP 的代码了6 C" U7 z% B* f2 M- l+ Y$ k
    2 z' K7 D" W8 l; P, M* j
    - f6 n3 P% L2 A$ ]2 o  n
    int KMP(String s,String t)
    1 s3 [2 _9 u  l4 T( l{
    ; g' P! k) x2 B  z8 X( `: q4 n1 Y   int next[MaxSize],i=0;j=0;9 x0 U: w# V  L8 C: W" `) a/ d
       Getnext(t,next);' ~$ l( {/ M0 R$ N2 h& ]
       while(i<s.length&&j<t.length)$ d/ C% x  t' }
       {9 D0 M6 m) d+ C7 o  m
          if(j==-1 || s==t[j])% _0 ^! c- ^# B
          {3 O. ^- N: Y. \+ p8 F
             i++;
    0 ]( u# N7 x2 M/ R* t9 I. s         j++;
    - J  l" U4 f3 H9 l! y      }
    . b, G! [) c3 w; n4 S2 X! o$ ?      else j=next[j];               //j回退。。。
    8 n: f1 S& P* G2 I+ R   }$ N$ [  {' O( E; T) C
       if(j>=t.length)6 |) j# G2 T0 C; l+ s! ~  u' D  a
           return (i-t.length);         //匹配成功,返回子串的位置8 R5 U/ d$ L3 ?! o
       else1 n- o' o' Z' \7 b9 ^5 g) ?- t; I
          return (-1);                  //没找到& m3 X3 h. ~, e9 M9 L6 M
    }
    # D4 i) F4 }9 H/ E1 h" q5 b' s; `$ _/ ~2 @+ ]  e
    改进后的 next 求解方法
    ( K4 |7 w1 v4 `5 F- e先来看一下上面算法存在的缺陷:
    * o" R6 \4 b& c1 k 6666.png # [7 k3 U/ Q! C5 h5 N( b" M" U
    显然,当我们上边的算法得到的next数组应该是[ -1,0,0,1 ]
    1 m% m2 {: [( c  @2 h+ y, ^5 L  R2 o

    # V2 Y& b0 q3 K2 P5 _. M/ L2 j) b2 {所以下一步我们应该是把j移动到第1个元素咯:' P/ t( V: h" U% ~3 N7 w
    7777.png
    * @+ c: q& i4 j, B% [* G; d  |2 f不难发现,这一步是完全没有意义的。因为后面的B已经不匹配了,那前面的B也一定是不匹配的,同样的情况其实还发生在第2个元素A上。
    4 N9 x" k% T8 R2 G' r
    0 s- f& K3 E7 J& U1 O0 {

    - m9 R4 U* u5 `5 T6 e( h显然,发生问题的原因在于t[j] == t[next[j]]。
    3 b, B; l8 ~1 n' I$ `7 A1 a" m5 z1 `8 }* q% ]0 T& m7 v
    8 r/ k# c7 k& b/ D
    所以我们需要谈价一个判断:
    6 J" ?, X- V7 y4 S* b8 U9 g1 Q, B' h9 ?
    & Y+ ?: L9 Q9 W4 I; D7 w( o' M

    ) }0 c8 Z( c- A& Lvoid Getnext(int next[],String t)
    0 ^7 p- F/ S* h% m3 b$ a& |+ Y0 m9 Z{
    4 T! ]0 C3 z9 x  o. J4 I; \% b# A   int j=0,k=-1;2 X& S" V* w  x6 H. \& x
       next[0]=-1;; L0 ^/ s$ K- e5 L
       while(j<t.length-1)
    2 Z9 F4 M5 |5 i( J' v& W- ~2 P7 Z   {$ M/ V, H2 z: M3 b$ `$ U
          if(k == -1 || t[j] == t[k])% V+ J5 f' ]0 G. q! t
          {
    ; L2 f7 G, |- e) l         j++;k++;( o& k  V% [  [+ f2 F5 ~2 N
             if(t[j]==t[k])//当两个字符相同时,就跳过/ j; E0 H/ y! @# y
                next[j] = next[k];
    8 }6 n3 L0 V% g, g         else0 l- p8 [. D3 \
                next[j] = k;' e5 R) R" R5 L2 a/ X
          }
    7 m$ U+ Q/ X8 }/ y6 G! f      else k = next[k];* c1 q6 g( T' [  ?8 a$ |0 \% D
       }: s$ v, g8 ~- k0 V2 @- d, {( G0 Q3 O) y
    }
    + X& x' G7 `9 ^3 w: N1 y————————————————& ^; P7 n+ b  A7 r1 M) ~. g# I" M# }
    版权声明:本文为CSDN博主「June·D」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    ' O* J8 u( D& x4 S原文链接:https://blog.csdn.net/dark_cy/article/details/88698736
    ) C9 g* p$ y! J( |6 u% u0 M) q; m9 D, V0 Z/ R6 ~: w+ ]' J
    ' s9 O3 Z/ v5 q: C9 m# u
    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-9-25 09:52 , Processed in 0.539434 second(s), 59 queries .

    回顶部