QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 2727|回复: 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; E& ]1 d" c* ^) G# \
    简介: L& B% K/ D. C+ Q0 M& f
      KMP 算法是 D.E.Knuth、J,H,Morris 和 V.R.Pratt 三位神人共同提出的,称之为 Knuth-Morria-Pratt 算法,简称 KMP 算法。该算法相对于 Brute-Force(暴力)算法有比较大的改进,主要是消除了主串指针的回溯,从而使算法效率有了某种程度的提高。
    2 L" |1 @* [1 {0 L2 r% w. F5 g4 a7 s
    ) D4 R" X/ N. @1 |; l+ p  c$ e
    ; p$ R9 g- U. L# Y) W
    提取加速匹配的信息- {: j1 f3 E8 o* g
      上面说道 KMP 算法主要是通过消除主串指针的回溯来提高匹配的效率的,那么,它是则呢样来消除回溯的呢?就是因为它提取并运用了加速匹配的信息!# ^( J9 p4 D5 |2 z; t
      这种信息就是对于每模式串 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 }。
    3 {3 W, [5 a4 ^# r% t4 C9 n    b: Z# B* P4 b. @
      加速信息,即数组 next 的提取是整个 KMP 算法中最核心的部分,弄懂了 next 的求解方法,也就弄懂了 KMP 算法的十之七八了,但是不巧的是这部分代码恰恰是最不容易弄懂的……
    % W# Y- F5 f1 b4 A5 U6 G! n8 _* K  + u' C( W, U2 R
    先上代码% J5 \1 z- [% Z8 p

    $ D2 i) ?; O! v& X+ H

    3 j9 c9 @. m/ R! w& Ivoid Getnext(int next[],String t)
    9 W7 ~8 t/ V+ X0 J6 e$ H{
    6 T9 @/ d7 Q/ @; I, ~   int j=0,k=-1;
    - A( B7 [) c: _1 |   next[0]=-1;: R5 v- b3 w: l8 b" m7 K* g
       while(j<t.length-1)
    / E5 z+ e1 E7 `" W( W8 Q. R! f   {6 b7 Z) d) n! h) z+ G
          if(k == -1 || t[j] == t[k])
    8 X7 B) q3 S0 R+ n% j      {
    1 z7 D6 ?9 y8 `* x2 Y9 F# k         j++;k++;
    5 m% a" K6 A  R5 d9 Z; Z         next[j] = k;
    , j7 |- M0 b6 P# Z      }
    9 w) ?3 M8 c. e4 j+ S, A. g9 i2 K      else k = next[k];//此语句是这段代码最反人类的地方,如果你一下子就能看懂,那么请允许我称呼你一声大神!
    + V5 ]& N% v4 [' M   }
    5 a# [& f. y( t! _7 q}- q: Y1 H1 a  U7 G+ O- @6 ?
    1 T1 @) ^0 f  ^# G- j3 v/ x5 h% E
    ok,下面咱们分三种情况来讲 next 的求解过程$ [: J2 v% T- A! S3 f
    ) u6 }+ Z# u/ d& D7 k5 J: m; G2 i
    6 @7 Y: `8 n: u1 X
    特殊情况1 q* k) e/ N1 ?9 x0 E0 x; ]8 u
    当 j 的值为 0 或 1 的时候,它们的 k 值都为 0,即 next[0] = 0、next[1] =0。但是为了后面 k 值计算的方便,我们将 next[0] 的值设置成 -1。% V" |# k8 _6 A( ]. |8 }5 ]! j
    . Z6 z) i  j+ }$ ]% M

    : j4 o0 ~; F' l, Y8 `+ b当 t[j] == t[k] 的情况
    2 q0 R' R! T: G: U, d8 k1 V举个栗子
    + d- b5 N, ?* |! T# ]5 x; K 1111.png
    ! F! |! y9 O3 m' T* @6 W2 ], y观察上图可知,当 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。
    ; H! [$ Z+ n4 {6 S6 j3 i: L4 S4 D  _: N# X- H3 f

    0 y, T$ |' ?" q; o# s当t[j] != t[k] 的情况) x) h- s$ B/ k6 H1 h
    关于这种情况,在代码中的描述就是“简单”的一句 k = next[k];。我当时看了之后,感觉有点蒙,于是就去翻《数据结构教程》。但是这本书里,对于这行代码的解释只有三个字:k 回退…!于是我从“有点蒙”的状态升级到了“很蒙蔽”的状态,我心想,k 回退?我当然知道这是 k 退回,但是它为什么要会退到 next[k] 的位置?为什么不是回退到k-1???巴拉巴拉巴拉…此处省略一万字。
    " F" y/ H" U/ D- F- S0 d( A( e9 a: `; O) G% g

    ( a5 b7 b$ t7 M2 C& g% [/ x1 x9 M我绞尽脑汁,仍是不得其解。于是我就去问度娘…) Z: e+ _# [* N- Z0 }4 F
    在我看了众多博客之后,终于有了一种拨云见日的感觉,看下图* r" O4 h  f0 l: L: W+ M
    2222.png 6 @  Y! n8 R, Z. s
       由第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。
    + p. f" s2 x1 m: t. Z% l
    7 G) `  z: I  }

    ' i: k9 o! u9 e3 J至此,算是把求解数组 next 的算法弄清楚了(其实是,终于把 k = next[k] 弄懂了…)
    1 V7 a+ {, M2 `- [
    3 y! k2 i2 B6 ]6 @; f5 H# K& c& G
    8 l8 n; A4 J; M5 S
    因为这个算法神奇难解之处就在k=next[k]这一处的理解上,网上解析的非常之多,有的就是例证,举例子按代码走流程,走出结果了,跟肉眼看的一致,就认为解释了为什么k=next[k];很少有看到解释的非常清楚的,或者有,但我没有仔细和耐心看下去。我一般扫一眼,就大概知道这个解析是否能说的通。仔细想了三天,搞的千转百折,山重水复,一头雾气缭绕的。搞懂以后又觉得确实简单,但是绕人,烧脑。8 I) X! O' ]- w# z5 s# n$ V

    ( h5 e# E# _3 [1 [$ n9 i) @" i6 h( a! c7 _

    ' E; {% O& V, h8 a9 _再此特别感谢昵称为“sofu6”的博客园主,正是他的博客,让我这愚笨的脑袋瓜开窍了
    ' x8 Q( Q, u9 A4 B7 v3 P
    % @4 n+ H8 w: c& V

    + C/ \5 m& e- a! ~9 NKMP算法实现3 w5 S; ~  A  u9 w; [" h
    当你求出了 next 数组之后,KMP 算法就很轻易搞定了,下面我用三张图,让你明白 KMP 算法完成匹配的整个过程。$ t  @1 J  f9 ~% a. h
    以目标串:s,指针为 i ;模式串:t 指针为 j ; 为例% @4 D' u# ~5 F8 e5 Z8 `2 c0 X3 y3 ^
    3333.png 7 v+ N( O' f/ `/ `
    上图表示:“si-j ~ si-1” == “t 0 ~ t j-1”,s i != t j(前面都相等,但比较到 t j 时发现不相等了)且next[j] == k。4 C9 s  N" x3 [* o8 V. ?% n7 j: ?/ H
    4444.png ! k" B! e& M$ G4 O. }, E* @2 ^( d
    根据 next 数组的定义得知 “t k ~ t j-1” == “t 0 ~ t k-1”,所以 “t 0 ~ t k-1” == “si-k ~ si-1”) m' F6 i+ _4 R" C( P* ^- y
    555.png * J( r7 ]  j; J: a
    将模式串右移,得到上图,这样就避免了目标穿的指针回溯。
    # j, S# v& t  ]  F8 ?
    6 {8 G/ o2 E: e6 H* @

    2 J; w1 v0 I# ~, N2 }, b0 e; Q都明了之后就可以手写 KMP 的代码了
    / o/ k, }7 k/ I8 e( Y9 d7 j9 w8 m" A
    5 R6 Y; C" r7 q% ?% a8 y( _
    int KMP(String s,String t)
    - {0 B6 a: l* m1 q" ^& d" r6 f{" H$ [; g. T4 ~9 F" \1 H5 F
       int next[MaxSize],i=0;j=0;# l! R+ g- t3 D; T9 D
       Getnext(t,next);
    ; ~) G: i2 P4 n   while(i<s.length&&j<t.length)
    4 P- V# J% l' X2 h, ?( Y7 q5 G5 q   {
    $ v. z) E4 K" E& |# c" P9 L1 h      if(j==-1 || s==t[j]), W/ U$ i! p! V; C# d% }( l
          {7 |$ e" `! I. H- U% o5 C. }3 Q0 p
             i++;) m7 O0 L8 ^! b1 {1 X" q4 ~
             j++;
    : G( r& E  o* }9 k      }& I% E0 }- \+ _3 p# h
          else j=next[j];               //j回退。。。
    , U& w6 O- n) q  w7 P2 g   }) r' n! v9 `1 g! J$ y
       if(j>=t.length)2 Y0 z3 a+ B4 n/ M: L. R( I& R6 X
           return (i-t.length);         //匹配成功,返回子串的位置% F% d  s  E  H6 j6 |, g/ m
       else4 W. h# E( n1 J
          return (-1);                  //没找到) F$ F5 S/ e: o! h4 b+ O) L# c7 H* M
    }
    $ H7 J4 x; [4 p& S& F: x; Z
    ! N; ?5 b. {$ Z  ^8 u, Z  L改进后的 next 求解方法
    % p; M" _9 N3 E7 j" G3 \先来看一下上面算法存在的缺陷:9 U5 {# J  }( _& K( U8 z% [8 [2 ]
    6666.png
    , @1 E1 k8 `0 q, P' }3 ?7 e显然,当我们上边的算法得到的next数组应该是[ -1,0,0,1 ]4 o8 s% q; ?, k9 h8 i9 `' t

    2 H/ s" ]' |, ~) {$ m

    " X- |5 }2 o: C2 x. @+ D所以下一步我们应该是把j移动到第1个元素咯:
    ; g# n& X. `& N) ~0 O. a" ~# ]' ` 7777.png
    ( X+ B4 y& j; M* y! P3 Q不难发现,这一步是完全没有意义的。因为后面的B已经不匹配了,那前面的B也一定是不匹配的,同样的情况其实还发生在第2个元素A上。
    0 u( s' U" ]  t& Z) z9 W3 l" R, b0 Z) D( }
    . U0 X: G+ o' V- E
    显然,发生问题的原因在于t[j] == t[next[j]]。
    . d! _+ ?9 x& f' B+ T9 e! L9 _% a8 U, l9 s
    / U% k& z. T/ t( F0 f, I
    所以我们需要谈价一个判断:
    * z- Y, a- ~7 A( S5 l3 `: X/ P
    ) s& x, b; D% l& J+ b2 k' K  G

    " ]# l+ J+ v+ svoid Getnext(int next[],String t)
    1 c/ n& z5 ?, N1 ]9 L6 s# m{: k* c9 e3 q8 Y  t( ~! P
       int j=0,k=-1;! N5 ^3 v/ l+ E4 c" y# m
       next[0]=-1;
    1 {  T) H8 ]8 y' x) a% j+ L# F# {   while(j<t.length-1)
    3 w9 d' u! Z, M8 m; J; \   {4 i0 q. A6 s' h4 W
          if(k == -1 || t[j] == t[k])8 k0 D& Q$ \+ X/ L6 a
          {( r. V3 e$ ^+ b' Y& K
             j++;k++;5 |, [% o% c; v3 C. K# p
             if(t[j]==t[k])//当两个字符相同时,就跳过7 b4 S! d2 e4 g) J5 O) Q
                next[j] = next[k];* Q/ c+ s9 T" I! e0 v: p! q
             else
    5 D) D9 L7 ?' j7 q8 j# E5 B            next[j] = k;
    4 p& M+ }. ]5 z2 v; B+ M      }4 \: t5 F1 y* A8 t/ K9 V1 d( k
          else k = next[k];
    + p+ o! v! U; S& y- ^   }/ A: H* @% p1 M
    }. W/ k" L& z1 z) C# D
    ————————————————: E# U  e+ a6 {# ~( _5 d
    版权声明:本文为CSDN博主「June·D」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。5 E9 w+ B( y1 u- Q; u4 X
    原文链接:https://blog.csdn.net/dark_cy/article/details/88698736
    / i& q' W" @+ t- q( W. v* A. r
    ( P- c7 \" x+ V9 p' F
    : n$ X8 z; C1 p, b- [2 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, 2026-6-17 06:04 , Processed in 0.453799 second(s), 59 queries .

    回顶部