QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 2477|回复: 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算法—终于全部弄懂了% S0 U& ^4 s' s
    简介
    # {8 |* r$ h: N% \  Q& v  KMP 算法是 D.E.Knuth、J,H,Morris 和 V.R.Pratt 三位神人共同提出的,称之为 Knuth-Morria-Pratt 算法,简称 KMP 算法。该算法相对于 Brute-Force(暴力)算法有比较大的改进,主要是消除了主串指针的回溯,从而使算法效率有了某种程度的提高。1 u) m% o0 p- ]1 j( n9 h, A( e

    . H6 V# f! Z2 e
      J9 G1 f. s( e) q8 B
    提取加速匹配的信息7 f9 j. I' U4 t7 ]; y9 Q3 ~5 {
      上面说道 KMP 算法主要是通过消除主串指针的回溯来提高匹配的效率的,那么,它是则呢样来消除回溯的呢?就是因为它提取并运用了加速匹配的信息!8 l: l2 z1 z: {$ {) l" _
      这种信息就是对于每模式串 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 }。; H8 ?7 b  T; ^9 [
      9 c  b5 G# }2 y4 I* `
      加速信息,即数组 next 的提取是整个 KMP 算法中最核心的部分,弄懂了 next 的求解方法,也就弄懂了 KMP 算法的十之七八了,但是不巧的是这部分代码恰恰是最不容易弄懂的……  h, H! E$ d% ?2 e& [0 a5 ~
      : H, v0 E. e2 h3 Y. v
    先上代码
    / B2 _- R& j& M$ X' U! n" K' L9 g1 v5 u, s. `2 u9 D

    2 D$ P& P2 e' N; W6 \void Getnext(int next[],String t)
    : p2 s: z1 @& Q{& H- J" T/ z- d5 i
       int j=0,k=-1;
    + A8 |, ?, A- i8 v5 G   next[0]=-1;# p* Q9 V" ^5 R0 Y* d! i7 l
       while(j<t.length-1)
      Y; o& X9 h* H0 Q; [& a+ E   {
    3 \& ~4 t# Z+ t& o, e% F) M      if(k == -1 || t[j] == t[k])
    1 n5 }# w  U5 \5 `      {" s: ]+ k& Q: _! M% m9 e' I# a+ p
             j++;k++;
    0 F6 v" @1 G' q) _) U) T  R  n         next[j] = k;8 j) O$ {* X/ m0 y1 B! \8 U) y0 s
          }9 t; @" }4 a& ~
          else k = next[k];//此语句是这段代码最反人类的地方,如果你一下子就能看懂,那么请允许我称呼你一声大神!; D# z6 [$ ]* {4 n
       }
    ' s  A, ^; R9 }4 Y7 b}
    , t/ j* ^- b8 j/ I
    ; m; Y% e3 W- \0 O! h) g& h, nok,下面咱们分三种情况来讲 next 的求解过程
    6 `8 b" a5 S" c6 B# \8 ]5 ]6 \- N

    4 ?- t( H4 n% I+ l特殊情况6 u" T! x$ O, D# c1 E5 N+ h
    当 j 的值为 0 或 1 的时候,它们的 k 值都为 0,即 next[0] = 0、next[1] =0。但是为了后面 k 值计算的方便,我们将 next[0] 的值设置成 -1。' O! W/ ?( f1 ~
    : K1 z: E/ O' E( k! s
    : o4 i" Y. ^3 C3 B) L/ T" Q
    当 t[j] == t[k] 的情况
    % V0 f: p% U$ W  [' y+ W" y举个栗子9 I5 @: k5 J9 K9 o  ~/ o: _% \" }
    1111.png ( g# E" d2 M+ {# W: }& y7 L
    观察上图可知,当 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。* I7 l! t( V, I7 {: T

    $ x% j+ r0 B, }/ B

    6 d7 Y& S  A& t! d5 ~- p当t[j] != t[k] 的情况% z% N% z8 w% G! O+ _# X
    关于这种情况,在代码中的描述就是“简单”的一句 k = next[k];。我当时看了之后,感觉有点蒙,于是就去翻《数据结构教程》。但是这本书里,对于这行代码的解释只有三个字:k 回退…!于是我从“有点蒙”的状态升级到了“很蒙蔽”的状态,我心想,k 回退?我当然知道这是 k 退回,但是它为什么要会退到 next[k] 的位置?为什么不是回退到k-1???巴拉巴拉巴拉…此处省略一万字。9 d* `' \# [+ L$ }3 a3 q; B
    * x" r- |0 W+ t' w

    # x+ q- G8 t) k9 }# _4 i我绞尽脑汁,仍是不得其解。于是我就去问度娘…- n; ]2 Z6 H2 U- I
    在我看了众多博客之后,终于有了一种拨云见日的感觉,看下图
      B% G, O* Y! L0 ^3 M+ j 2222.png 4 s4 `* C% j3 i7 e7 x
       由第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。
    ! u$ u; q' }" D3 S7 `; v
    - ?0 q9 N% c( K" U7 Y
    ( X; K. W' h8 W2 }
    至此,算是把求解数组 next 的算法弄清楚了(其实是,终于把 k = next[k] 弄懂了…)% M6 c' P2 {  W  J. H, U

    6 W  ]) ~: j2 N# L  s5 V
    " s/ N8 o3 b' P+ D+ h" F6 R
    因为这个算法神奇难解之处就在k=next[k]这一处的理解上,网上解析的非常之多,有的就是例证,举例子按代码走流程,走出结果了,跟肉眼看的一致,就认为解释了为什么k=next[k];很少有看到解释的非常清楚的,或者有,但我没有仔细和耐心看下去。我一般扫一眼,就大概知道这个解析是否能说的通。仔细想了三天,搞的千转百折,山重水复,一头雾气缭绕的。搞懂以后又觉得确实简单,但是绕人,烧脑。* U( x5 m6 }& g" e& ]
    + ?: ]$ l0 v/ ]" Y/ u' B  _3 R6 W# ~

    ( b* x! z+ s* o: i2 X再此特别感谢昵称为“sofu6”的博客园主,正是他的博客,让我这愚笨的脑袋瓜开窍了
    $ o  B( s- x# I- r# V$ n4 B# _/ p) k8 ], l8 O; i9 c8 O% y0 `) V

    . m( y) C: |. @# W. }KMP算法实现0 g7 C$ d; M6 Z
    当你求出了 next 数组之后,KMP 算法就很轻易搞定了,下面我用三张图,让你明白 KMP 算法完成匹配的整个过程。% K( F1 ?; o; U- @( ]
    以目标串:s,指针为 i ;模式串:t 指针为 j ; 为例+ T; G# r. w) |4 ~/ G: E. I
    3333.png
    ) C+ ?7 E1 ?6 ~) a9 y8 z上图表示:“si-j ~ si-1” == “t 0 ~ t j-1”,s i != t j(前面都相等,但比较到 t j 时发现不相等了)且next[j] == k。
    / o; z( E  |' |9 u 4444.png
    9 c3 [1 c# ?; t8 D" j2 a7 T根据 next 数组的定义得知 “t k ~ t j-1” == “t 0 ~ t k-1”,所以 “t 0 ~ t k-1” == “si-k ~ si-1”
    2 @" q7 M4 H$ y, G  [- C# ?6 x 555.png
    5 \8 m' t8 h: }) Z, \将模式串右移,得到上图,这样就避免了目标穿的指针回溯。
    ) @/ J: e) B9 i3 P8 O) B+ x. G# Z2 s$ e  p* R: V0 |
    # {2 q4 X+ U+ Y
    都明了之后就可以手写 KMP 的代码了1 }/ g9 J& S7 ]/ C2 T6 f  e3 j
    - D' L9 g! e# {9 n  w# g* b
    ( A3 R5 E1 E  a7 G6 ?6 x
    int KMP(String s,String t)
    + w$ K$ \+ `4 M6 M4 u( A{
    % c9 E! |- z" g% X; R1 ^* P4 i   int next[MaxSize],i=0;j=0;- L1 r9 L+ @( ~) h; ~8 ~2 ^
       Getnext(t,next);+ R$ t/ J" F" I+ Y+ `3 x; u* g$ Q& I3 G
       while(i<s.length&&j<t.length)6 Q8 V% V9 W9 M9 @4 c! [7 g8 p7 n$ `9 n2 M
       {
    6 v9 N; N  v, X" |( K  q  m6 m4 [# u      if(j==-1 || s==t[j])* m  b! q' c. D5 s% K) R8 m
          {0 S. F. l+ |2 e( S+ t
             i++;
    4 v5 u+ M5 _* c) E         j++;
    : x+ M- z  m- D. ?  m5 i      }3 \1 e: a. A. \* U- c
          else j=next[j];               //j回退。。。  ~1 S( Q2 |5 A
       }
    0 r9 Z: j- w. c  a/ H  o   if(j>=t.length)" w- Z2 B' e- X+ S% N
           return (i-t.length);         //匹配成功,返回子串的位置
    ; N" _/ l1 B' Z( O6 V   else
    $ W- y$ S. ^1 i% z      return (-1);                  //没找到
    6 |6 E* c3 ~* _5 q6 G}1 J* t6 r$ ]7 N6 h) @. O

    2 H2 w8 j" p  Z改进后的 next 求解方法7 z7 |5 u; V! @- C; ?' J9 x9 n5 r
    先来看一下上面算法存在的缺陷:8 n# {/ C: Z3 e+ `3 u
    6666.png ! H* S: @/ u" F; i
    显然,当我们上边的算法得到的next数组应该是[ -1,0,0,1 ]& m4 T; K+ N( \8 q+ X8 G+ u3 I
    6 Y1 C$ W$ Q; E! ?# l* O4 Y: n

    $ c6 [( j9 E9 ?; D' k所以下一步我们应该是把j移动到第1个元素咯:
    + M! f1 X* R, W* _' x 7777.png
    8 B; I' N4 U) k4 s1 {不难发现,这一步是完全没有意义的。因为后面的B已经不匹配了,那前面的B也一定是不匹配的,同样的情况其实还发生在第2个元素A上。
      c2 K$ c$ w2 y! j8 L7 @& @3 @, s8 r3 h) C4 [
    / S/ u, E* t! h1 V2 ^
    显然,发生问题的原因在于t[j] == t[next[j]]。
    6 K0 |; ~: Q! x; p6 I
    1 r% K. x/ n3 [' h! ?9 i: j& X

    ! ?& R) f' l& ~* @0 O* e& ]所以我们需要谈价一个判断:% Q; E+ V# F; g; ?* m7 k" L

    ; m" f$ [. y0 n% O* U' x1 N/ y

    ; m+ @# V8 y# T% e  o/ Wvoid Getnext(int next[],String t)% y3 E- u- b7 }9 T1 ^/ q& Q
    {
    6 Z  I- p0 d9 O2 N   int j=0,k=-1;
    6 g$ G! i% a3 f9 t+ f   next[0]=-1;
    " }6 x  f0 }$ i1 R9 ^# Y- V) }   while(j<t.length-1): {) y2 W0 q- j/ L- i) `$ C3 d
       {( z" M; b0 \" z. ]1 |
          if(k == -1 || t[j] == t[k])3 W- @1 {4 |5 g
          {
    4 D6 w+ Z# Z8 W9 `3 f         j++;k++;
    : S+ {1 k0 x; G) T9 y         if(t[j]==t[k])//当两个字符相同时,就跳过
    ; t# z  Z0 M5 a1 @2 V            next[j] = next[k];' T' v5 y# S$ a' U1 X
             else
    1 {  h; Z: j2 Q$ N+ A8 [* ~/ p* R& }. }            next[j] = k;
    # N9 g1 x7 F9 S" k2 m+ {6 W      }
    ( O- x. ~4 F, \  b4 L& c* o      else k = next[k];; |0 h8 o# N' g- \9 y5 u
       }
    . W' L5 f' o5 Y* I7 E/ ^}
    7 M  h6 B. f* N. M! e9 y6 i————————————————
    : [8 R* l( D! H2 ~* {" y+ x% k* k版权声明:本文为CSDN博主「June·D」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    / R  Y- w, L" p$ ~3 A/ O原文链接:https://blog.csdn.net/dark_cy/article/details/88698736
    $ u" P! p9 s. J- P( r. C- L& X9 W

    ) y( I4 f* S  H6 F/ X1 r9 L
    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-14 06:41 , Processed in 0.571257 second(s), 58 queries .

    回顶部