QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 2443|回复: 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算法—终于全部弄懂了' H/ b% F" \9 Y1 Q! b9 @/ z* B
    简介+ W2 @' n" K, l
      KMP 算法是 D.E.Knuth、J,H,Morris 和 V.R.Pratt 三位神人共同提出的,称之为 Knuth-Morria-Pratt 算法,简称 KMP 算法。该算法相对于 Brute-Force(暴力)算法有比较大的改进,主要是消除了主串指针的回溯,从而使算法效率有了某种程度的提高。
    " ^" R  T. X8 u" {% q6 k6 n- E4 T$ `& ]4 X3 U2 a

    2 v% @# E  b: r% K; z6 T提取加速匹配的信息
    ; {/ K4 R0 Y" v( ~- }9 h  上面说道 KMP 算法主要是通过消除主串指针的回溯来提高匹配的效率的,那么,它是则呢样来消除回溯的呢?就是因为它提取并运用了加速匹配的信息!
    ; [) N& G. g% 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 }。' c  p( x, \( W9 |2 h5 `" }
      
    * Z$ \% E$ k# m6 v: \! y, D  加速信息,即数组 next 的提取是整个 KMP 算法中最核心的部分,弄懂了 next 的求解方法,也就弄懂了 KMP 算法的十之七八了,但是不巧的是这部分代码恰恰是最不容易弄懂的……
    0 _5 k7 A7 A: B' |  T5 s* I  & j0 t9 N/ J* i3 m
    先上代码
    ! {) v( \* Q# Y1 b$ N; K7 W& f4 a& H3 o- E9 r& V
    . q8 D) t! ?* G) D, m" d0 n: x
    void Getnext(int next[],String t)
    ; g9 \( R% v( |0 [% S3 B{: U8 M/ Z* `& I* o5 i) A6 w
       int j=0,k=-1;" A  n- r4 L( Q- Z- p  x$ k
       next[0]=-1;
    6 C7 t5 r* }, L5 V5 o7 p5 ?' n6 v6 z0 e   while(j<t.length-1)1 z  r5 P' Z1 x! |+ ^/ U
       {
    4 b% g6 h+ p, G- X" U0 ~; D      if(k == -1 || t[j] == t[k])
      A" a& B1 Q& J' }" L* A1 |      {6 Z; m- s) j" w4 l; H
             j++;k++;
    1 ]4 [" {- A& h         next[j] = k;, l0 X* W! W9 A; C6 v' Z2 M
          }/ N- C+ [# v+ X  s/ D* p
          else k = next[k];//此语句是这段代码最反人类的地方,如果你一下子就能看懂,那么请允许我称呼你一声大神!
    & z! g7 d- [  W9 o8 U$ M( A" R   }3 Z+ A1 ^6 V* z+ D
    }
    4 X5 z( z! N( h; l% m8 i6 Q6 D( `. R2 U8 q+ S# o2 o( e1 J9 g
    ok,下面咱们分三种情况来讲 next 的求解过程
    " l9 a; S7 ^% Y; U# G
    - J; ^- Q1 g4 x% N* D) u! Y9 ]0 P

    5 T4 F' K; H2 |  E特殊情况, e, u$ J: l" T- ?, v4 a' v
    当 j 的值为 0 或 1 的时候,它们的 k 值都为 0,即 next[0] = 0、next[1] =0。但是为了后面 k 值计算的方便,我们将 next[0] 的值设置成 -1。
    ; y& r0 z) b) C! q- s) K8 S8 u% j1 z  w! g# x0 z
    & N  K( }/ d5 \+ s! V
    当 t[j] == t[k] 的情况' R1 ?7 E$ S) U9 R0 u5 e
    举个栗子
    : j3 o# o8 G! o5 A( U* C( X! t5 g$ G& G 1111.png % n) g' }5 V' X  M2 W  x
    观察上图可知,当 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。8 @! O* i/ J" E: ^! i3 C. W

    ; o/ w2 f: Y8 {* ]" e7 s

    9 @! l+ K' _1 T" h3 E# [5 P4 X当t[j] != t[k] 的情况, K( Q3 d! w: [/ U9 G' e
    关于这种情况,在代码中的描述就是“简单”的一句 k = next[k];。我当时看了之后,感觉有点蒙,于是就去翻《数据结构教程》。但是这本书里,对于这行代码的解释只有三个字:k 回退…!于是我从“有点蒙”的状态升级到了“很蒙蔽”的状态,我心想,k 回退?我当然知道这是 k 退回,但是它为什么要会退到 next[k] 的位置?为什么不是回退到k-1???巴拉巴拉巴拉…此处省略一万字。
    % \4 x% D0 o+ E8 Z
    0 z3 ~4 Z% T8 N& w* K
    3 _& B) B- |$ c! d
    我绞尽脑汁,仍是不得其解。于是我就去问度娘…1 b+ X$ E$ |; f" U4 R
    在我看了众多博客之后,终于有了一种拨云见日的感觉,看下图% }4 g& [0 ~/ |: m  ?; c
    2222.png 9 f' _5 W& |  \8 h) m) W# 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。  B! p  @3 V/ y" c; k
    $ k) `  J' h) f! j& \( H9 F7 B6 T
    5 A, k: v9 D6 I. Y# h1 H4 t
    至此,算是把求解数组 next 的算法弄清楚了(其实是,终于把 k = next[k] 弄懂了…)
    + S' G( y9 ?# n% k' o9 E& k, E
    " Z5 I/ }4 j+ E4 |7 ~- E
    4 ?" z: Q$ _9 I0 t
    因为这个算法神奇难解之处就在k=next[k]这一处的理解上,网上解析的非常之多,有的就是例证,举例子按代码走流程,走出结果了,跟肉眼看的一致,就认为解释了为什么k=next[k];很少有看到解释的非常清楚的,或者有,但我没有仔细和耐心看下去。我一般扫一眼,就大概知道这个解析是否能说的通。仔细想了三天,搞的千转百折,山重水复,一头雾气缭绕的。搞懂以后又觉得确实简单,但是绕人,烧脑。
    2 M* b0 @% o: k  f( O
    + ^. |% \( |. l, m% Z! j7 X- }
    3 o: T. C2 G  d2 {2 E2 [$ q
    再此特别感谢昵称为“sofu6”的博客园主,正是他的博客,让我这愚笨的脑袋瓜开窍了% s6 _% `7 Y7 u, E3 X' d

    , W( W5 m/ O; A4 t0 C  p

    ) _9 q5 B( n3 vKMP算法实现
    4 k' I4 Y& n0 B6 k: _+ }; m: E: i当你求出了 next 数组之后,KMP 算法就很轻易搞定了,下面我用三张图,让你明白 KMP 算法完成匹配的整个过程。4 f" m0 Y7 g7 Z* U& E1 O+ A+ l
    以目标串:s,指针为 i ;模式串:t 指针为 j ; 为例" a" ~, E  j+ z5 R. |, W
    3333.png ! T1 T! x& d  E" `
    上图表示:“si-j ~ si-1” == “t 0 ~ t j-1”,s i != t j(前面都相等,但比较到 t j 时发现不相等了)且next[j] == k。) q" i6 x+ s  l7 L( k% P
    4444.png
    4 O0 w. U; S8 L6 C* T根据 next 数组的定义得知 “t k ~ t j-1” == “t 0 ~ t k-1”,所以 “t 0 ~ t k-1” == “si-k ~ si-1”
    " m. f# m0 X, @$ |* i; S( U 555.png
    " r3 s! H/ \2 K" b* E7 ]; ~将模式串右移,得到上图,这样就避免了目标穿的指针回溯。
    % T2 y: D1 E% W2 a
    ' ?8 a" k% ^! u& ?8 N+ ]% Q
    : _- Q2 f, U/ ^6 y5 A2 Z/ d
    都明了之后就可以手写 KMP 的代码了
      |4 D5 z: a: c7 L- G0 ]/ [; E
    ! I' ]( _: P4 z: r7 H! W! Y

    * B% f* a  ]3 t7 q* t* N' Sint KMP(String s,String t)4 u, M; l3 Y5 M! ^9 r+ s
    {
    7 Q- [7 b, v9 I5 Y8 @/ K8 b" O   int next[MaxSize],i=0;j=0;
    , [2 q4 h' L8 S( C& T" U   Getnext(t,next);/ F" j: ?1 {( c) P) q; F) h
       while(i<s.length&&j<t.length)& P- \( {1 ^8 C! ^+ m
       {( {( W" [8 Q) d' H6 C: Q) o' L) e
          if(j==-1 || s==t[j])0 P2 U3 r2 L# ~5 z' C
          {9 Q; k3 V7 g2 c$ b
             i++;
    ; g" H; r) n7 f9 Z         j++;
    " q+ N" G! e  W0 m1 K      }
    0 a6 Z2 o/ ~3 B" W% S7 l      else j=next[j];               //j回退。。。
    4 y5 ^: P: p. Z  J* \- p; p- M   }  i/ N1 X0 N$ i' Y& h2 X4 p( d
       if(j>=t.length)8 s2 p2 {: A5 n( J3 U
           return (i-t.length);         //匹配成功,返回子串的位置7 y) ?1 s6 X/ Z6 a
       else# }* U. [! }" U% Q
          return (-1);                  //没找到
    / Y  g% J$ D/ k$ |# v7 B  C2 C}9 u# n' q; D" e; G
    / s' w1 K+ j7 `8 e1 A
    改进后的 next 求解方法! r' |/ A2 }$ O$ ?* d1 U
    先来看一下上面算法存在的缺陷:8 `; D% x4 H) |; b% X- `* L; `
    6666.png 3 ]' b: i6 ~+ {
    显然,当我们上边的算法得到的next数组应该是[ -1,0,0,1 ]
    1 `+ l, f6 p- e% Y: g& M/ c9 G/ A+ N3 Z1 D" q+ j( L  k

      T6 ]9 D7 p1 A+ f所以下一步我们应该是把j移动到第1个元素咯:0 _9 s) @& w0 L8 I7 D1 z
    7777.png
    & c! H& A4 E  p不难发现,这一步是完全没有意义的。因为后面的B已经不匹配了,那前面的B也一定是不匹配的,同样的情况其实还发生在第2个元素A上。
    / V" c# W, h) b9 `
      A. D2 A. `; Z4 d; d- a- H

    # b7 b, v' n$ q; U6 h) ~显然,发生问题的原因在于t[j] == t[next[j]]。
    3 M3 j7 `: k% D  ]! c; C1 l2 v* k4 j; d7 b2 l( o' q. E
    5 n' L4 l% f4 R1 l+ l; m( D
    所以我们需要谈价一个判断:
    % Y9 _+ ^$ B; p: w( l
    0 X1 ~0 e; T' x" D# P  ?% {
    ( ~! E6 V$ L3 T% r" ?7 n
    void Getnext(int next[],String t)
    8 m/ h4 D+ j$ w: N0 _{
    % k$ U; |. U( d$ U9 F9 j+ f1 `# N   int j=0,k=-1;1 I. r; `4 ]9 o/ B  \4 h
       next[0]=-1;% T$ v0 f- w4 n! z& `5 B
       while(j<t.length-1)% Z. r! {; v( s# f: q# G, o# _& n
       {
    2 [+ t% A* d( R: Y% Y9 ]$ c      if(k == -1 || t[j] == t[k])
    + l5 x6 `3 D" `3 g5 k      {
    & H  ^6 @5 k# @: ?9 D! A- @         j++;k++;
    % x2 _! X2 }5 ]; O9 R! n- h         if(t[j]==t[k])//当两个字符相同时,就跳过
    ( t% y4 j+ c" ]' [9 _+ a* C            next[j] = next[k];3 ~% q* K/ z  P+ y& y0 m# Y
             else; N( Y% S7 [) C$ G
                next[j] = k;3 b! ?4 ^( B! W0 X) W  Q
          }& ~0 ~/ ~2 K& u+ K8 K
          else k = next[k];
    2 j0 [! O7 K3 K. S# F: |3 Z% q% g   }
    6 n3 c# f7 [/ Y0 R: K  ]2 O}: F6 l# {& k9 Z/ H- Y# d; _/ Y, I
    ————————————————
    , h. R1 F/ L$ o) Y+ r) j版权声明:本文为CSDN博主「June·D」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。6 p/ e7 ^2 t& D& \. B: d
    原文链接:https://blog.csdn.net/dark_cy/article/details/886987368 s! m) k4 p! g2 j
      S( v9 C) `8 Z/ s7 z1 \
    2 \3 d! u& X' q8 E7 l& s/ Z0 \1 @
    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-7-27 04:45 , Processed in 0.745469 second(s), 59 queries .

    回顶部