QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 2457|回复: 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算法—终于全部弄懂了
    - B+ |# Y. ]) y* @2 {0 ?, U简介  ?$ G. w9 }% Z( H9 x) j/ _
      KMP 算法是 D.E.Knuth、J,H,Morris 和 V.R.Pratt 三位神人共同提出的,称之为 Knuth-Morria-Pratt 算法,简称 KMP 算法。该算法相对于 Brute-Force(暴力)算法有比较大的改进,主要是消除了主串指针的回溯,从而使算法效率有了某种程度的提高。
    / \: A( J5 [9 \6 F* ~1 ]. c2 n' O0 `+ T* l8 I! v6 t7 P3 g
    * g, \& P2 X8 B( h
    提取加速匹配的信息* f& B7 V- B: H' L% i* K' U. p+ y
      上面说道 KMP 算法主要是通过消除主串指针的回溯来提高匹配的效率的,那么,它是则呢样来消除回溯的呢?就是因为它提取并运用了加速匹配的信息!# `4 c0 H( F* B$ |* z/ y* I
      这种信息就是对于每模式串 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 L& t5 d/ P: b4 a/ a6 I# L
      + Q, A: c7 e6 C9 I. P4 w; o
      加速信息,即数组 next 的提取是整个 KMP 算法中最核心的部分,弄懂了 next 的求解方法,也就弄懂了 KMP 算法的十之七八了,但是不巧的是这部分代码恰恰是最不容易弄懂的……$ z+ N9 w2 ?6 o0 z0 v6 d
      $ M$ ~8 S6 A" V0 ]8 |
    先上代码
    & e: @7 f7 o4 E+ k* h& Y
    " w6 L" g; c9 o  A  x# b8 d4 L
    8 `4 d! W- D0 L* c: N
    void Getnext(int next[],String t)7 a; C; b; _7 y2 j3 S8 l; _- |
    {; N; q5 ?) Q  [1 ^
       int j=0,k=-1;& c0 b- y& J7 O
       next[0]=-1;
    5 w+ k2 L7 ~& C   while(j<t.length-1)$ ]  a: q& G1 m- v
       {
    + R; g0 D5 {; X$ A. ^% D      if(k == -1 || t[j] == t[k])
    $ B# E  y' w6 @7 J+ V% k0 G      {) _( W2 I3 ^, u
             j++;k++;
    4 F1 K- u# @  o8 `) V2 V5 s         next[j] = k;& Y  a  b: {, }2 Q
          }0 J8 h/ @- }1 c1 }* C- e5 Q' ^5 {, Y
          else k = next[k];//此语句是这段代码最反人类的地方,如果你一下子就能看懂,那么请允许我称呼你一声大神!4 I% D2 Y5 B! g0 s
       }+ g1 Q" f5 y7 `; D
    }0 _+ N/ F1 y( {/ R
    ; X4 h8 |2 x8 d0 b- q$ B
    ok,下面咱们分三种情况来讲 next 的求解过程
    / F# M9 H) @2 _* q1 J, A3 x3 ~! Y. |$ U, v2 \" s
    * x/ f  v8 M: Q  `0 Y" a
    特殊情况
    / P- R( c5 c" A/ c当 j 的值为 0 或 1 的时候,它们的 k 值都为 0,即 next[0] = 0、next[1] =0。但是为了后面 k 值计算的方便,我们将 next[0] 的值设置成 -1。
    . g4 R* _: `$ Y% k8 I
    7 s; y1 i3 ~9 N, v! b
    " {3 j8 G5 R7 F8 o
    当 t[j] == t[k] 的情况, W, {6 O& Q2 T+ i/ N/ Q' @
    举个栗子" Z' e& m  l/ c+ c' ?
    1111.png / M1 H, @  O! k) t
    观察上图可知,当 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。5 Y; n$ y) {* l5 C3 Y5 j$ v

    0 u# b6 P% F; D* Y% @. S

    6 x) X5 K, j5 e- X1 q9 M当t[j] != t[k] 的情况2 ?, x9 w/ Z/ W+ L5 X
    关于这种情况,在代码中的描述就是“简单”的一句 k = next[k];。我当时看了之后,感觉有点蒙,于是就去翻《数据结构教程》。但是这本书里,对于这行代码的解释只有三个字:k 回退…!于是我从“有点蒙”的状态升级到了“很蒙蔽”的状态,我心想,k 回退?我当然知道这是 k 退回,但是它为什么要会退到 next[k] 的位置?为什么不是回退到k-1???巴拉巴拉巴拉…此处省略一万字。# G7 n& }0 R/ Q' ^; d- ~5 {

    ( K/ w: X& V- T2 M, L
    7 B' f  p; b8 y( x& `0 K  d! Q# e0 @7 O8 h, y
    我绞尽脑汁,仍是不得其解。于是我就去问度娘…
    3 J7 g) Y' x7 l- @. C在我看了众多博客之后,终于有了一种拨云见日的感觉,看下图
    : O" Q- \/ v0 T/ q9 F 2222.png
    & w7 b( \6 p: t  [   由第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。3 j; X: s) i* |9 T0 C
    7 w  e* Y0 n. I; X  V
    5 p- z; P0 ^5 _+ y
    至此,算是把求解数组 next 的算法弄清楚了(其实是,终于把 k = next[k] 弄懂了…)
      z& @  R6 }# |& z: {3 N, N' D& u& X
    / ^" j/ W& l; k) j1 V; ?( C: C
    因为这个算法神奇难解之处就在k=next[k]这一处的理解上,网上解析的非常之多,有的就是例证,举例子按代码走流程,走出结果了,跟肉眼看的一致,就认为解释了为什么k=next[k];很少有看到解释的非常清楚的,或者有,但我没有仔细和耐心看下去。我一般扫一眼,就大概知道这个解析是否能说的通。仔细想了三天,搞的千转百折,山重水复,一头雾气缭绕的。搞懂以后又觉得确实简单,但是绕人,烧脑。# g" }8 y; C. p% S2 a' g

    5 Q/ h/ V5 m: b) C
    ( |4 H% t5 Q2 B9 X. G) g
    再此特别感谢昵称为“sofu6”的博客园主,正是他的博客,让我这愚笨的脑袋瓜开窍了" B# `5 Z& v2 i0 M4 [
    & X7 _5 x. g: t4 X/ L' q

    $ f/ {. }# x9 d% wKMP算法实现8 o. `5 s. T- Q7 Z2 @# |- V
    当你求出了 next 数组之后,KMP 算法就很轻易搞定了,下面我用三张图,让你明白 KMP 算法完成匹配的整个过程。
    ; ?: D6 p- j  Q7 v3 Z4 J* k/ r以目标串:s,指针为 i ;模式串:t 指针为 j ; 为例
    ' V3 W# ~# N8 h7 m- E/ l 3333.png % C/ s* t  T7 q2 K* f! l
    上图表示:“si-j ~ si-1” == “t 0 ~ t j-1”,s i != t j(前面都相等,但比较到 t j 时发现不相等了)且next[j] == k。
    : b- b$ w3 N" ]- P! l: E 4444.png
    % A, i# F3 P" K0 G& e' {根据 next 数组的定义得知 “t k ~ t j-1” == “t 0 ~ t k-1”,所以 “t 0 ~ t k-1” == “si-k ~ si-1”) y% {" W4 z. ?& H0 o$ u
    555.png 3 W3 K( x" A! r2 H. s
    将模式串右移,得到上图,这样就避免了目标穿的指针回溯。
    $ \! g, d* a  r+ F& c' I  f/ [& I- J4 O, Z4 q( ~

    1 y% n+ ?, ?* R& b' m. f都明了之后就可以手写 KMP 的代码了
    5 M+ ]9 c, @: J1 Y: m
    5 t. G. ~0 W' t+ v, M6 s  }9 N
    # x5 z; j! B* ~; d6 b
    int KMP(String s,String t)
    : i' t7 s9 u0 y5 B7 I; f/ Q{
    ' R1 f) V+ V7 ~$ W. C! J1 K   int next[MaxSize],i=0;j=0;
    $ H6 J# e% ]- ^   Getnext(t,next);
    $ _/ l( |/ K) j, q  d   while(i<s.length&&j<t.length)
    0 G: \9 a* V6 u! p8 S+ m+ f# c   {
    ( |2 S2 e; B& a. E2 s      if(j==-1 || s==t[j])
    4 W. e1 L5 j* C' P1 W      {4 b: e# t( i1 M  s- t3 H
             i++;
    , g$ o5 [7 T! i, y         j++;" i: g. _* m% r8 A5 k3 Q3 C% z
          }
    0 L) K# v& J2 l) v0 ^; n      else j=next[j];               //j回退。。。: d8 l) ?) G; N$ c' v% V+ D+ s
       }
    0 a8 Y/ a( k& B: E$ I; T   if(j>=t.length)
    , |3 ]4 [) w0 F# C$ J: `/ J. {       return (i-t.length);         //匹配成功,返回子串的位置' ~# ]5 {/ a9 p8 q) n; R4 q- b; |9 k
       else
    / z9 g: s% N/ K* _3 u7 O) |      return (-1);                  //没找到
    * H, b0 O  R6 z1 U}/ o$ F" J- l+ x* `  D. ?

    7 u  \  _$ \% l/ O. C, B* P: ]* D改进后的 next 求解方法! g2 `' |" _; G8 X) v
    先来看一下上面算法存在的缺陷:
    , F0 H4 ~. Q3 k* x 6666.png   V. F5 A* h9 f0 U  e3 d: X
    显然,当我们上边的算法得到的next数组应该是[ -1,0,0,1 ]9 \' v1 u) X3 x) W2 C

    7 j; X- a( a1 Y( `+ b- l8 r
    * a, V! ^; ^, h+ ?4 H( s
    所以下一步我们应该是把j移动到第1个元素咯:  q1 {; y: i* g# Z
    7777.png ) ]( Z; B/ r; ~0 B" Q
    不难发现,这一步是完全没有意义的。因为后面的B已经不匹配了,那前面的B也一定是不匹配的,同样的情况其实还发生在第2个元素A上。! s$ P2 V0 d. I/ ~* y$ v

    % W* N8 b+ u& I  w/ H, d
    / Y8 ~- s- q% G- v7 Z
    显然,发生问题的原因在于t[j] == t[next[j]]。0 v: U% _) s5 g3 @* }# K8 p' D* _

    2 Z3 Y# d- B8 k! l$ ?+ O$ u. t
    * C+ r/ p) v% ]& N
    所以我们需要谈价一个判断:" I7 |" R0 g) b1 k/ w
    ! g9 e9 E) N: X7 T! G  K7 `
    4 Z# V+ c$ x5 W
    void Getnext(int next[],String t)
    / T5 Q* P' r2 c9 j{9 Z' C( Y" k% ^) Z% V3 l. ?1 }
       int j=0,k=-1;
    4 \; a" s% j  R: S- b# D0 Z   next[0]=-1;
    ) H- d6 X* h4 o   while(j<t.length-1)( W; Z4 V( S- D( r, q  M
       {
      p" t; z: X7 v( W      if(k == -1 || t[j] == t[k])
    8 g1 T+ }2 p# m3 Y; y7 s      {
    ! W) G9 i$ \: W, ^6 Y# S& f- a         j++;k++;# n- t) h7 S" [4 w5 _9 {! m
             if(t[j]==t[k])//当两个字符相同时,就跳过6 ?" z% m7 A4 Q& _# K
                next[j] = next[k];% w% z; y' A4 t+ a$ [& S8 @  p
             else
    0 h( S7 N) J0 ]) t            next[j] = k;
    1 o# u3 P/ f! V9 q$ K& N; B      }
    & b% m3 |- J3 I) p7 g$ N7 ^      else k = next[k];) U# h. x: w. u2 k- Z3 E* m! z
       }
    ' q* B8 H7 D; Y; s# [2 W0 h}
    1 J" q& T! o! c3 o" S, q————————————————
    ; ~9 w3 L) i* y1 A版权声明:本文为CSDN博主「June·D」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。. M- N( N6 I" B9 p
    原文链接:https://blog.csdn.net/dark_cy/article/details/88698736& F7 @* B$ S7 H" U

      ~& d6 g' R: G1 z& w8 D8 |/ d6 [1 s2 n+ N, `- i
    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-5 02:59 , Processed in 0.392539 second(s), 59 queries .

    回顶部