QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 2730|回复: 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算法—终于全部弄懂了; ~2 S! e# z7 Q8 h1 B( T
    简介( \6 B4 B& w, i, \0 b, t
      KMP 算法是 D.E.Knuth、J,H,Morris 和 V.R.Pratt 三位神人共同提出的,称之为 Knuth-Morria-Pratt 算法,简称 KMP 算法。该算法相对于 Brute-Force(暴力)算法有比较大的改进,主要是消除了主串指针的回溯,从而使算法效率有了某种程度的提高。, T$ }; I% ^* V8 ?0 c# C

    , j$ G: j' J3 I) f' U
    2 l6 i; I. k4 G  b% b9 j
    提取加速匹配的信息8 Z9 z2 L$ ]) S* ]
      上面说道 KMP 算法主要是通过消除主串指针的回溯来提高匹配的效率的,那么,它是则呢样来消除回溯的呢?就是因为它提取并运用了加速匹配的信息!
    ; K* Y$ w. o* {$ w! K  这种信息就是对于每模式串 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 }。
    9 T: j$ x" z5 C8 L  / q  @1 I, L  F2 ~6 o* H8 N
      加速信息,即数组 next 的提取是整个 KMP 算法中最核心的部分,弄懂了 next 的求解方法,也就弄懂了 KMP 算法的十之七八了,但是不巧的是这部分代码恰恰是最不容易弄懂的……
    # e) I7 Y# B0 k4 c2 m' V  
    7 M. u+ F% I% K- L+ X6 b先上代码
    8 _# n) Q0 o- u* S# W' g8 ~5 ~& x
    $ p9 k8 ]5 \- C$ h9 \9 \. d
    void Getnext(int next[],String t)) g  A  j4 O% _& M4 c( j7 @
    {/ C- `# R7 I6 H! t
       int j=0,k=-1;, P# ?, g7 J, {9 \  g$ D$ o
       next[0]=-1;
    . i' @- L: q9 B6 I0 `1 t   while(j<t.length-1)5 v7 i7 d! x, v& {, M% M/ y& ~% u
       {* `% r! }) T  Y
          if(k == -1 || t[j] == t[k])
    " i- a! I! E& Q9 [, v9 S- O& K      {
    ; E$ b4 X$ Z5 T         j++;k++;; C. b" M6 ~$ t6 D
             next[j] = k;
    9 u1 J7 w: H" O* g9 N% p. g      }" p: _' {" p7 g: A. v$ }: w
          else k = next[k];//此语句是这段代码最反人类的地方,如果你一下子就能看懂,那么请允许我称呼你一声大神!# m6 B/ `$ Q: B: q# v
       }/ J: J" k1 _) w9 x2 G
    }
    3 r& t/ [& K4 ]! k
    , _3 U- ~+ @* h) Q3 Jok,下面咱们分三种情况来讲 next 的求解过程3 P1 l  h+ o! L$ h/ [& T
    4 I% M& A$ o0 [8 g+ }. w& H7 F
    ) {% P& R! Q8 C0 N$ }" Y$ n' q
    特殊情况
    + T" U( i# h7 m% Q0 g当 j 的值为 0 或 1 的时候,它们的 k 值都为 0,即 next[0] = 0、next[1] =0。但是为了后面 k 值计算的方便,我们将 next[0] 的值设置成 -1。) M; b, K. d8 ^9 o( y, B/ w
    2 ?: J2 R. ?" s

    , w7 c0 c( }  _4 n: v当 t[j] == t[k] 的情况
    " {) @9 U' t- h% L7 h举个栗子
    ) v- X9 d8 D3 d8 \" @ 1111.png   h# p4 s1 V% |% P
    观察上图可知,当 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。
    6 _1 Y- p% Z. ]( J6 w7 r
    ! h1 g6 }0 L. f+ v$ `( g; F
    : |4 Y$ A# A* m! v- J9 ~& d
    当t[j] != t[k] 的情况
    8 j. X. r# d( u; B: y7 e' {关于这种情况,在代码中的描述就是“简单”的一句 k = next[k];。我当时看了之后,感觉有点蒙,于是就去翻《数据结构教程》。但是这本书里,对于这行代码的解释只有三个字:k 回退…!于是我从“有点蒙”的状态升级到了“很蒙蔽”的状态,我心想,k 回退?我当然知道这是 k 退回,但是它为什么要会退到 next[k] 的位置?为什么不是回退到k-1???巴拉巴拉巴拉…此处省略一万字。
    3 n! ^# @6 Q9 f* t: L
    . Z6 n7 R; b% \6 ~: c* t2 v
      ~7 ~$ w. S* Q8 J- ?% E
    我绞尽脑汁,仍是不得其解。于是我就去问度娘…
    6 l4 g& [& Z4 _$ P8 Q在我看了众多博客之后,终于有了一种拨云见日的感觉,看下图
    9 P% Y2 V5 R# [+ x: ~: n  p) J6 o- | 2222.png 8 V  F$ p$ 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。
    9 M! U- z+ \2 K- a/ F; A- R/ ~1 M8 J5 e
    3 P. z( U! s5 L% h7 k$ d
    至此,算是把求解数组 next 的算法弄清楚了(其实是,终于把 k = next[k] 弄懂了…)
    / {7 P& w. N9 d  l+ _5 i  R$ W  m; v4 g! [" r
    ' z6 W5 s: ^, |: L0 `
    因为这个算法神奇难解之处就在k=next[k]这一处的理解上,网上解析的非常之多,有的就是例证,举例子按代码走流程,走出结果了,跟肉眼看的一致,就认为解释了为什么k=next[k];很少有看到解释的非常清楚的,或者有,但我没有仔细和耐心看下去。我一般扫一眼,就大概知道这个解析是否能说的通。仔细想了三天,搞的千转百折,山重水复,一头雾气缭绕的。搞懂以后又觉得确实简单,但是绕人,烧脑。2 C/ p4 N7 W8 ^5 N  ^, s
    : N5 V0 _& \, v; ]2 C8 ?8 a

    2 J  _7 o  g$ m/ Z( _& b再此特别感谢昵称为“sofu6”的博客园主,正是他的博客,让我这愚笨的脑袋瓜开窍了$ J7 @+ z. N% r% I2 l

    . b& W1 D/ b( X  O, R

    : K$ ?# f, z$ ]KMP算法实现+ R* x2 c# j, L
    当你求出了 next 数组之后,KMP 算法就很轻易搞定了,下面我用三张图,让你明白 KMP 算法完成匹配的整个过程。8 T; h1 Z4 H' ]
    以目标串:s,指针为 i ;模式串:t 指针为 j ; 为例
    8 v# N8 f* L6 K 3333.png ' b4 E: D1 a% m( G2 x# E8 I
    上图表示:“si-j ~ si-1” == “t 0 ~ t j-1”,s i != t j(前面都相等,但比较到 t j 时发现不相等了)且next[j] == k。# \; d& S& V, S( {: Z
    4444.png
    3 i3 }* [7 J7 y/ M2 ?根据 next 数组的定义得知 “t k ~ t j-1” == “t 0 ~ t k-1”,所以 “t 0 ~ t k-1” == “si-k ~ si-1”& B4 y$ T1 N- Y5 l; f& q
    555.png % Z4 D) N9 }9 q7 m0 k$ h/ t2 c
    将模式串右移,得到上图,这样就避免了目标穿的指针回溯。& H! w7 s  Y) D) \$ A3 Z

      t2 B7 R/ F9 L' N' w( Q- }

    9 W0 P+ E2 c7 k7 H9 ?, x都明了之后就可以手写 KMP 的代码了" t6 z8 r/ M- k% e5 Y" n- n" w0 N

    / e: E. t  H# d0 Y, K1 R# C* V7 b

    . I& {# X6 h" J2 lint KMP(String s,String t)) x6 q1 k( K, G/ U; @& r
    {' r" ?, [, Z* n: d: T
       int next[MaxSize],i=0;j=0;: h2 \! P! @7 Q5 Z  n
       Getnext(t,next);
    1 |, z/ L9 E* ?  `+ @' C9 V; m   while(i<s.length&&j<t.length)
    7 H9 W2 D/ I. I   {
    ; l; X6 x/ n5 e2 H% z4 E* a8 P9 f      if(j==-1 || s==t[j])
    9 D( w2 m7 u3 L, c4 }      {) X' b  h& B) I9 ^+ E  s
             i++;( d1 j' C) p* G" y
             j++;1 R( c% d% w2 U- b/ E/ |& @, G$ r
          }
    2 D: v* }- R5 V8 H2 ~! K      else j=next[j];               //j回退。。。1 I9 A9 Z! H/ F) c  C3 K
       }" N- x% }7 v/ T8 R6 Q1 b! J  T
       if(j>=t.length)0 V' @6 M; f3 p' _8 a
           return (i-t.length);         //匹配成功,返回子串的位置
    ( g1 w/ k6 p  `! G' }+ `   else) _8 v- P: n; o' A" q
          return (-1);                  //没找到
    $ h) m6 r! @0 e+ q- L) {- }, q9 U}; p8 w+ |8 N. ]- t( F" B7 E; \

    - N" }$ j  a0 O$ f# ^: U改进后的 next 求解方法0 r# ?3 `: F5 ~, C5 t7 T
    先来看一下上面算法存在的缺陷:3 W6 o# N) I/ P/ u3 s9 b
    6666.png
    , L/ |( Q' ^- K7 R! V+ z3 I. D显然,当我们上边的算法得到的next数组应该是[ -1,0,0,1 ]# n3 p0 p0 k7 Y! I; I# v1 i! X

    1 [: }0 b1 w; l, B6 e
    # T. w" T  N" r5 k: k- E
    所以下一步我们应该是把j移动到第1个元素咯:( ^) a2 z9 F. T9 M, [; a4 R0 m
    7777.png
    # g* n" n, |) f  d* g不难发现,这一步是完全没有意义的。因为后面的B已经不匹配了,那前面的B也一定是不匹配的,同样的情况其实还发生在第2个元素A上。
    8 j1 c: W; ?8 h9 v: |" g+ I
    ( v: W  M9 C# B" F8 G0 U& @. U
    ) K3 e0 r5 ^; i2 F
    显然,发生问题的原因在于t[j] == t[next[j]]。
    + H. Y" Q1 [! S# T' h3 H; ]
    ) q' |$ N& {% c0 h0 T

    ; [- |% b) Q2 l1 s% [所以我们需要谈价一个判断:! R4 ^, E, }/ F9 B: N1 @

    . l4 x9 P6 `3 `7 z  j+ l4 m% f

    , Y6 Q% J# P  R: Rvoid Getnext(int next[],String t)
    7 S( G' X' O( H: d& e3 {3 x. c( P9 b{
    ' |3 P* }3 z+ k   int j=0,k=-1;
    ! q) ~5 l/ B; O# P. }* S1 S   next[0]=-1;9 r. z; m- U& E4 V0 \( w8 R) W
       while(j<t.length-1)$ M7 V/ b* S# `, h4 K% k9 e; q
       {
    - b1 Y9 h, Q! S' H' t- M6 G( M      if(k == -1 || t[j] == t[k])
    1 ~* g5 R) d: o9 F( @- l, N. f      {
    ; ~& _% q) K; @6 l9 _2 \6 M% [         j++;k++;
    / z8 c& ~+ ~: o7 L' F& s& j- Z. R         if(t[j]==t[k])//当两个字符相同时,就跳过
    - j+ P. @( A3 }1 A            next[j] = next[k];6 O0 }7 J  @/ ]5 j, p
             else
    + @$ u$ p( W0 I! Z            next[j] = k;
    - @8 n* Q. S* v0 t1 I: g# v      }
    8 Z  }# d+ N* F5 `7 q1 h; L      else k = next[k];- r8 q. |3 y1 U* B& C; n$ C1 `
       }4 e5 c. M# K3 D' ?: u' _* e
    }
    ! f, v2 o# V, Q7 `1 J6 z+ I: E————————————————. b9 L) u/ C: }2 W
    版权声明:本文为CSDN博主「June·D」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。& ~6 u; ]0 t+ Q$ C) h% C, z
    原文链接:https://blog.csdn.net/dark_cy/article/details/88698736
    7 Q4 [& s% ^) o, o% X: D
    4 v9 k* a" o/ X# B$ B
    0 {& \- G7 u. I0 T
    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 17:48 , Processed in 0.383211 second(s), 58 queries .

    回顶部