QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 2522|回复: 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 h0 G+ y& W9 J简介* D% U" V( r+ K6 g
      KMP 算法是 D.E.Knuth、J,H,Morris 和 V.R.Pratt 三位神人共同提出的,称之为 Knuth-Morria-Pratt 算法,简称 KMP 算法。该算法相对于 Brute-Force(暴力)算法有比较大的改进,主要是消除了主串指针的回溯,从而使算法效率有了某种程度的提高。1 Q0 f  t' I( S! h& V' `
    $ c8 ^% D1 U' C
    8 B1 X+ b5 i" H) o) r
    提取加速匹配的信息
    " ?0 ~' I4 M# D' k1 L6 Z  上面说道 KMP 算法主要是通过消除主串指针的回溯来提高匹配的效率的,那么,它是则呢样来消除回溯的呢?就是因为它提取并运用了加速匹配的信息!
    : ^, l. ^: ~+ v' S5 M. S: 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 }。
    . x4 ]! {" @9 m6 K  : K: k0 B+ z* e5 X) f0 B9 t; a
      加速信息,即数组 next 的提取是整个 KMP 算法中最核心的部分,弄懂了 next 的求解方法,也就弄懂了 KMP 算法的十之七八了,但是不巧的是这部分代码恰恰是最不容易弄懂的……: m$ }9 C$ ]+ Z2 S
      
    / T7 G* I- i% k( g先上代码
    ) V) H3 O' L4 y9 u* k9 F- W0 }
    ( _4 E6 v/ v) s, ?& P& ~( f3 }, p
    3 S1 _0 u5 d+ w
    void Getnext(int next[],String t)1 x: Y* V$ n$ v' t
    {- q8 u* k' r  l, C- i: {% j
       int j=0,k=-1;1 ?; A) u, X2 Y+ C
       next[0]=-1;1 a. ~# R, O  U* k
       while(j<t.length-1)
    5 C* f) S9 e4 k) s2 ]   {- s4 c3 h3 Q* G0 a% C' t4 e  l
          if(k == -1 || t[j] == t[k])
    3 u! v! ~6 t) D( h2 E  c3 q6 B      {% g' R; E% d( h. t3 n
             j++;k++;
    ' A5 C* U" y/ _& B6 [0 ^* `+ t         next[j] = k;
    . R2 ^4 J) m! H5 `      }9 }( p) Y$ i, e' y- u& f+ \
          else k = next[k];//此语句是这段代码最反人类的地方,如果你一下子就能看懂,那么请允许我称呼你一声大神!
    4 e; }7 I, e8 A2 E1 p2 ~1 ~2 \   }
    2 f$ o7 \+ T; \% M}/ U+ Y& [4 N5 w8 B" ^; ]  P

    6 Z7 R; t$ n1 Wok,下面咱们分三种情况来讲 next 的求解过程
    7 N! C* ]" G6 U' o+ x% R3 y! |( _: i+ e1 y; r
    ; F& D' O4 c# a+ q6 \( v3 k9 E0 N
    特殊情况
      G" Q$ Q! U# ^: o  m当 j 的值为 0 或 1 的时候,它们的 k 值都为 0,即 next[0] = 0、next[1] =0。但是为了后面 k 值计算的方便,我们将 next[0] 的值设置成 -1。
    0 Y2 z! p# B. Q5 t
    ' }" [  k( _) Y- p: F$ s

    ; c" O* y! }+ J5 x* j! H: W当 t[j] == t[k] 的情况
    - y: r: m# R* K% R- E举个栗子
    - @' B2 S' W3 N4 {: t; E4 l 1111.png 4 \6 g  ?7 h# E, [( b
    观察上图可知,当 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。1 J$ a; j3 L7 c" x6 W+ L- }
    : ^( o6 Z+ [7 D/ |. j* a

    ; g' X. D, O  e当t[j] != t[k] 的情况
    $ g% S* x: T( p( V' ?, @关于这种情况,在代码中的描述就是“简单”的一句 k = next[k];。我当时看了之后,感觉有点蒙,于是就去翻《数据结构教程》。但是这本书里,对于这行代码的解释只有三个字:k 回退…!于是我从“有点蒙”的状态升级到了“很蒙蔽”的状态,我心想,k 回退?我当然知道这是 k 退回,但是它为什么要会退到 next[k] 的位置?为什么不是回退到k-1???巴拉巴拉巴拉…此处省略一万字。
    4 ?6 K1 r/ c% h' V
    ! u7 \, `: d4 m; a& F

    - Z- V6 K4 _, E8 X& ~1 U7 F我绞尽脑汁,仍是不得其解。于是我就去问度娘…- O2 O; m& t' ?0 t+ L
    在我看了众多博客之后,终于有了一种拨云见日的感觉,看下图( I8 l+ ~$ h& f! D
    2222.png + ]' v  M. L) K5 ]! I& c1 K3 i
       由第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。
    4 e; m, h0 B2 q& d% Y
    ) S4 n. J4 a. S/ K  A( q: c
    , `- F+ }2 D; M! h' E9 A3 B
    至此,算是把求解数组 next 的算法弄清楚了(其实是,终于把 k = next[k] 弄懂了…)
    " l, o1 ?* v& Y) n: ]% F: H- A2 E) w& ?2 j1 E% ^

    2 P& ?* [; W, y& X+ o. T因为这个算法神奇难解之处就在k=next[k]这一处的理解上,网上解析的非常之多,有的就是例证,举例子按代码走流程,走出结果了,跟肉眼看的一致,就认为解释了为什么k=next[k];很少有看到解释的非常清楚的,或者有,但我没有仔细和耐心看下去。我一般扫一眼,就大概知道这个解析是否能说的通。仔细想了三天,搞的千转百折,山重水复,一头雾气缭绕的。搞懂以后又觉得确实简单,但是绕人,烧脑。
    : e, @7 u: ~" c3 b" O
    ( E9 ?4 H8 r% b) y
    $ x9 |9 W' `% |8 ]8 j6 I( t3 J$ G5 h
    再此特别感谢昵称为“sofu6”的博客园主,正是他的博客,让我这愚笨的脑袋瓜开窍了! g" }' I9 w) G3 Q4 q8 t! h
    4 i5 c0 t2 B/ q2 C6 R# p4 s
    , j% R5 P0 F6 x" }
    KMP算法实现
    - w& x  b$ ~0 |8 w1 C当你求出了 next 数组之后,KMP 算法就很轻易搞定了,下面我用三张图,让你明白 KMP 算法完成匹配的整个过程。9 y9 E4 @1 ^/ W0 [4 Q1 n- A& v
    以目标串:s,指针为 i ;模式串:t 指针为 j ; 为例
    * ~1 R$ C* B& H! b* i 3333.png
    ! }$ o3 ~$ M& F" p& B上图表示:“si-j ~ si-1” == “t 0 ~ t j-1”,s i != t j(前面都相等,但比较到 t j 时发现不相等了)且next[j] == k。
      i  y' T9 G, v" V7 F 4444.png ; G* ~1 p1 t' D# ~
    根据 next 数组的定义得知 “t k ~ t j-1” == “t 0 ~ t k-1”,所以 “t 0 ~ t k-1” == “si-k ~ si-1”( A( X. ~* a# X1 U7 B1 p2 h
    555.png ) k2 }& C/ c: B- C6 w4 i
    将模式串右移,得到上图,这样就避免了目标穿的指针回溯。+ A' O' V8 g% X& f% F- a) D
    ( F6 v% R& R0 R& T# X

    ; v, C1 h8 j* t' K4 t都明了之后就可以手写 KMP 的代码了( X9 T/ T$ v6 Y: c* t" ?5 D
    8 P2 n/ Y4 G) I# \" n& }) O

    ) o3 G" M' S* w3 `. uint KMP(String s,String t)* }9 z* b2 g: A% t: q+ u5 w+ t
    {
    # Y% y! p/ f' v   int next[MaxSize],i=0;j=0;
    3 a- _) I! \* Z" F1 L   Getnext(t,next);
    * E( D, P4 C: V$ w) b   while(i<s.length&&j<t.length)
    " }+ A0 L1 l  Q+ p; B   {
    9 l* }% U/ t3 m3 D5 Y8 e      if(j==-1 || s==t[j])# N# l  {9 F+ A3 p# o1 Q0 ?1 O
          {$ \& T9 Y& Z& o# k+ P& D
             i++;# [4 f8 o2 r/ O% Q- Y7 {% S
             j++;' E6 H; R1 s" s# ~6 x+ l
          }& \, s" k- T, @9 o; X. F/ G
          else j=next[j];               //j回退。。。, J/ J$ z, M. j5 g! O( ~: u
       }) {/ B- T: H: m: k
       if(j>=t.length)
    ( |' h& j- p+ l" g       return (i-t.length);         //匹配成功,返回子串的位置9 v$ C7 r$ q- K5 `+ b
       else
    4 j6 B) s2 ~9 D! u3 z$ |+ R      return (-1);                  //没找到$ a3 r% X! B% o% I
    }, g" j  _0 ~8 x/ H
    - V4 P" T  @4 [. J0 i
    改进后的 next 求解方法
    , {' A2 u3 f# n# w( a: B7 q先来看一下上面算法存在的缺陷:
    $ [" m, ?/ P8 c- v2 V0 s 6666.png 0 u8 Z! x; o, K; `
    显然,当我们上边的算法得到的next数组应该是[ -1,0,0,1 ]* u% |) j" e- V; b2 d$ y/ j: S

    9 i. {/ h" f% ^

    * P" d3 c! O% L2 y" [2 E所以下一步我们应该是把j移动到第1个元素咯:
    ) V! o' P( k" D. L; y1 j+ n. ~ 7777.png " W' y" Y- Y4 I, J6 g" E/ p
    不难发现,这一步是完全没有意义的。因为后面的B已经不匹配了,那前面的B也一定是不匹配的,同样的情况其实还发生在第2个元素A上。/ x1 s" [4 Y% E) X
    " x/ L* }9 `6 [% T. T- X) _1 e7 J' Q

    4 j( a* z6 i6 W( L- W2 H显然,发生问题的原因在于t[j] == t[next[j]]。
    9 }$ [* i8 `0 `$ }3 e8 [
    0 B7 V  k# G0 c
    / R- o, @4 J1 o: a6 v: u
    所以我们需要谈价一个判断:/ Z8 @. c1 d, E0 C8 ^
    ) w* D. _$ |5 ?3 D  \# j

    $ N# _7 V0 @/ ]$ q4 }0 E$ _void Getnext(int next[],String t): Z( X- H7 C  }3 D' q
    {
    8 t9 r4 M9 P. d$ d! L( ^   int j=0,k=-1;! W& }2 \' S+ D8 T, P- C
       next[0]=-1;+ [1 B! f  k- S! u3 G- X  I
       while(j<t.length-1): t+ [0 u) _& [7 h
       {
    6 |1 {% m& S8 o      if(k == -1 || t[j] == t[k])
    , C0 }2 |# {, c# U3 Q% g      {
    # c" ~5 u# Q: h3 I9 i         j++;k++;
      `$ s( k" J6 `( p, p: A$ u9 O( u         if(t[j]==t[k])//当两个字符相同时,就跳过
    , |& `+ |9 P; c9 r3 y7 l            next[j] = next[k];
    : X. }5 f' }: }9 G8 X3 i$ M4 J$ |* M         else
    ) m: J' {' B  }& I3 _            next[j] = k;2 P9 @4 `& L/ U5 G' t+ h. w$ N
          }
    ( x( V, w5 Y3 g% O  G$ a      else k = next[k];* R- o/ d- ^; Z" Q. r+ c
       }; m& J- Q1 X7 e6 }; [$ I0 ?8 p  K9 J
    }9 w) ?) N! e& a( ^! C, G
    ————————————————6 U6 f; K: |4 l! i! X* O/ s
    版权声明:本文为CSDN博主「June·D」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。0 B8 G1 T3 f) u. d) @
    原文链接:https://blog.csdn.net/dark_cy/article/details/88698736; R" b% _4 j6 D3 V5 v# }

    1 |" v% U+ \9 o' a7 A9 k0 {  @8 R. V/ h* O% V5 R- }$ Z3 X4 c
    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-9-25 08:14 , Processed in 0.469519 second(s), 58 queries .

    回顶部