QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 2756|回复: 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算法—终于全部弄懂了6 @" X8 L2 f7 Z& d
    简介
    & S  x  Q- x' [& F8 v3 n3 H  KMP 算法是 D.E.Knuth、J,H,Morris 和 V.R.Pratt 三位神人共同提出的,称之为 Knuth-Morria-Pratt 算法,简称 KMP 算法。该算法相对于 Brute-Force(暴力)算法有比较大的改进,主要是消除了主串指针的回溯,从而使算法效率有了某种程度的提高。
    3 e* _  o3 p; d& P7 ^% g* B5 ^9 }. I- \; U) ?
    ! q$ Q; f7 \# l% T- E4 R
    提取加速匹配的信息/ U  s! y% n; z, B
      上面说道 KMP 算法主要是通过消除主串指针的回溯来提高匹配的效率的,那么,它是则呢样来消除回溯的呢?就是因为它提取并运用了加速匹配的信息!+ C) \+ D8 F9 @; l. t  ^/ K  A4 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 }。
    6 @( r4 B* x4 Q0 }+ W  
    7 W9 O1 ]% c$ G. {. H  b( D6 M  加速信息,即数组 next 的提取是整个 KMP 算法中最核心的部分,弄懂了 next 的求解方法,也就弄懂了 KMP 算法的十之七八了,但是不巧的是这部分代码恰恰是最不容易弄懂的……# `: p  l  S. f( @% z8 P
      
    1 T3 p  O: u+ D/ d- c+ k先上代码
    0 Q9 s) C7 R' I0 p2 C3 A( z# c) M4 e1 T5 G, X( W8 S
    ; i5 ^. V+ w; D
    void Getnext(int next[],String t)
    / m+ f$ ]7 u3 q, J' d; i5 b$ H+ q{
    * Q0 q! p% f5 q4 Z  _% q   int j=0,k=-1;
    1 D0 d2 {, d; I% p: d4 M# m4 [4 U   next[0]=-1;
    7 B% z4 i% k- W5 g4 o: K, G   while(j<t.length-1)! C& g$ `1 \- M6 ?, S$ W
       {8 {/ T0 P1 K7 e& e- D5 B
          if(k == -1 || t[j] == t[k])
    . A+ H3 y/ F9 H" [$ v0 T& y/ T8 z      {
    # @$ W6 |3 _5 x/ ]* V7 H         j++;k++;
    0 D- j( h5 _0 y6 @* @  q, k0 h3 Y         next[j] = k;
    & Y; v" [$ P; Q7 k: z' P- o1 w( G      }
    $ _& m: D- u" x  P* P( i; W      else k = next[k];//此语句是这段代码最反人类的地方,如果你一下子就能看懂,那么请允许我称呼你一声大神!
    ' f4 @% p2 L* u; h) \3 t   }* r3 q# [4 n0 I+ a9 }. V$ o' D
    }
    % U; ?/ j; F7 a0 q8 l7 N" m+ d; H+ M& h# l
    ok,下面咱们分三种情况来讲 next 的求解过程7 X! J+ V6 j& f) B7 W; z: M6 Y

    & ?; V7 h$ c+ y8 T7 \, Q
    7 W% }! p. ~0 n6 d
    特殊情况
    / J+ `7 q6 N( C9 n当 j 的值为 0 或 1 的时候,它们的 k 值都为 0,即 next[0] = 0、next[1] =0。但是为了后面 k 值计算的方便,我们将 next[0] 的值设置成 -1。5 R3 b. [) F! b9 Y5 k9 b3 s

      A. Z- `1 U9 X4 G/ U

    ) A6 f( ~$ A4 q, a% ^( a当 t[j] == t[k] 的情况2 `2 C" O; V$ b4 _" p
    举个栗子
    ) V2 E: w0 z9 F* K! c  T 1111.png - I  a4 F3 F4 _+ n' A, T5 a
    观察上图可知,当 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。* J  @8 B0 S; |8 t& i; X5 y( b

    5 Q3 P1 U/ @0 r

    ) s" J' m3 Z- L7 ~) w当t[j] != t[k] 的情况
    4 y; }; Y+ l0 T- |( V: o2 A: `关于这种情况,在代码中的描述就是“简单”的一句 k = next[k];。我当时看了之后,感觉有点蒙,于是就去翻《数据结构教程》。但是这本书里,对于这行代码的解释只有三个字:k 回退…!于是我从“有点蒙”的状态升级到了“很蒙蔽”的状态,我心想,k 回退?我当然知道这是 k 退回,但是它为什么要会退到 next[k] 的位置?为什么不是回退到k-1???巴拉巴拉巴拉…此处省略一万字。
    8 ^& s1 {2 X) @* d. f$ T8 K
    0 B! p: I9 _9 c% a+ q$ \
    6 y5 }* H. {6 I
    我绞尽脑汁,仍是不得其解。于是我就去问度娘…% l, X. l7 P$ W, W
    在我看了众多博客之后,终于有了一种拨云见日的感觉,看下图
    0 {# J  q% r- d( a9 Z/ [ 2222.png & q- U5 u- l$ S" [2 ]
       由第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。
    * u3 F* F; V% x) U# g3 |5 I) m( x, c: X+ U& l
    $ m$ ?4 k: Y$ ~2 G2 O2 a
    至此,算是把求解数组 next 的算法弄清楚了(其实是,终于把 k = next[k] 弄懂了…)
    0 e6 \/ ]$ ?2 I" A9 n! g7 {  P( i7 b/ R( q7 J4 f2 f$ `3 h% {7 _% x

    & Q! O3 B' `' t3 B因为这个算法神奇难解之处就在k=next[k]这一处的理解上,网上解析的非常之多,有的就是例证,举例子按代码走流程,走出结果了,跟肉眼看的一致,就认为解释了为什么k=next[k];很少有看到解释的非常清楚的,或者有,但我没有仔细和耐心看下去。我一般扫一眼,就大概知道这个解析是否能说的通。仔细想了三天,搞的千转百折,山重水复,一头雾气缭绕的。搞懂以后又觉得确实简单,但是绕人,烧脑。( J1 g7 n6 Z6 x4 e7 t) T" h
    7 L+ b9 t" w( l. D+ @, X7 Z! k
    + M$ o$ j0 N1 _
    再此特别感谢昵称为“sofu6”的博客园主,正是他的博客,让我这愚笨的脑袋瓜开窍了
    $ s) q1 s* F$ A/ G8 J6 m8 H) _( S) [( ?5 [7 G

    # n4 U# H7 }% f0 f+ OKMP算法实现
    $ s, m" j$ q9 f. v当你求出了 next 数组之后,KMP 算法就很轻易搞定了,下面我用三张图,让你明白 KMP 算法完成匹配的整个过程。' B9 S# W* W6 w2 z, Q
    以目标串:s,指针为 i ;模式串:t 指针为 j ; 为例1 L! A9 W' y/ _/ F+ B
    3333.png / B* h! |" D- z7 h0 D! u
    上图表示:“si-j ~ si-1” == “t 0 ~ t j-1”,s i != t j(前面都相等,但比较到 t j 时发现不相等了)且next[j] == k。2 c% H% O' _! T# \
    4444.png 9 I; L( v9 \" ~  \
    根据 next 数组的定义得知 “t k ~ t j-1” == “t 0 ~ t k-1”,所以 “t 0 ~ t k-1” == “si-k ~ si-1”9 a% G5 S& M' C
    555.png ! F; ~6 Z2 B; I
    将模式串右移,得到上图,这样就避免了目标穿的指针回溯。, t& i3 J# S" v7 d
    4 Y% g, [1 u# I! V9 E
    8 j( H0 R! t& u; D7 B- J6 F
    都明了之后就可以手写 KMP 的代码了
    % P* d- B' Y0 N5 j. C- k9 @- |4 L& a& x, u: i- o

    & Z. U3 V) c$ o! h+ h2 _int KMP(String s,String t)% ~4 L- z6 J6 u+ o+ V
    {
    0 P- x4 \/ k. x9 R" J8 [   int next[MaxSize],i=0;j=0;
    2 R6 n; O. l4 \   Getnext(t,next);/ {* \; }( v, K' e/ \6 E
       while(i<s.length&&j<t.length)
    & o/ c( O  u2 E) T  x+ y   {
    ! k. H% ^) [* d% Z3 t8 d      if(j==-1 || s==t[j])
      b6 J; u( X& L+ o% f2 e* B      {  y% r' e7 o: M; v- \
             i++;( {6 ]5 u( I) D1 n( I$ \0 l5 J( c
             j++;
    6 i! D. W0 y" B7 w& n, z* v1 a! w7 d; u      }2 Z3 \9 \& S% ~+ ?  d
          else j=next[j];               //j回退。。。4 |' d2 c* V* ~' M6 c+ o, Y" Y! U1 V
       }
    - h5 M) S4 Q* v& p$ ~- g   if(j>=t.length)) J$ G) I8 C; T" ]
           return (i-t.length);         //匹配成功,返回子串的位置, }$ o; j3 P0 j0 T1 j) c& \+ Z9 f6 ^
       else0 [( C2 K! I) g1 z6 W; @
          return (-1);                  //没找到* B# ?+ Q* \$ Z  |$ I
    }8 H* v  k  \$ z6 g4 }) ?, B
    9 I% T' ~, c+ U) O; [
    改进后的 next 求解方法
    , @! u! @5 ]$ a# f: V' O5 m先来看一下上面算法存在的缺陷:
    5 j( A0 n, {0 G; ?$ b 6666.png
    ! P5 k$ ^( Q7 V% }显然,当我们上边的算法得到的next数组应该是[ -1,0,0,1 ]
    6 X0 D' u% o* e6 q1 V( r- |! \; V9 b  S5 d" Q

    ! ~; h0 A  X/ f* a- p: u7 Q0 _0 f所以下一步我们应该是把j移动到第1个元素咯:( C+ d, ]  o. c/ w1 ~3 @
    7777.png
      J* n5 X! P* e  r( g不难发现,这一步是完全没有意义的。因为后面的B已经不匹配了,那前面的B也一定是不匹配的,同样的情况其实还发生在第2个元素A上。3 b, _% j9 \- D8 p

    + N5 I( S" a5 N( m7 n) a
    1 [& t3 t3 f" D# C! a
    显然,发生问题的原因在于t[j] == t[next[j]]。, M' t- j( u/ [0 k
    # I" n, b% d& i3 ~! |
    4 U; M/ z$ v! d, b) X/ F
    所以我们需要谈价一个判断:1 [* N1 r+ V; M. y, `
    8 h/ E8 w1 _: h3 ]
    - v6 K) [) x5 Z; E8 x  B4 \
    void Getnext(int next[],String t). R0 d% K2 h  H4 j* f$ a
    {9 n* r3 U- }4 c, L  |. E& U1 }& e
       int j=0,k=-1;5 x7 f0 H% a) F. f$ h
       next[0]=-1;
    1 B) M' G" V; F& G   while(j<t.length-1); ?, Z0 ?9 k4 R* G+ B
       {
    / r$ P, P  X3 q# D" N      if(k == -1 || t[j] == t[k])0 i& h' f) U# H) M9 Y, k! {0 S
          {
    % ?% p  a! U* A1 l% L( o$ I5 H2 M" S         j++;k++;
    : n" i, d5 ~9 ~9 b. o$ H         if(t[j]==t[k])//当两个字符相同时,就跳过5 D$ Q3 v& U) [, a4 G" O% V( Q' ?; U
                next[j] = next[k];% X, g  L1 Z6 Y+ u7 c
             else
    " t" U: B% \. P( [/ v4 P            next[j] = k;
    2 p- y" j  i+ a* D      }1 K" @3 G  N0 w* h1 A$ o
          else k = next[k];
    3 I1 |+ k' ^9 w   }
      B; n% }2 J$ j4 d. D) z6 o}
    5 d1 y9 F9 r1 Q" W) h! |) a————————————————7 }7 Y( ?) W/ t% S0 H  |# f& y
    版权声明:本文为CSDN博主「June·D」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。2 R& R& J+ a3 _/ j5 y! D" Q
    原文链接:https://blog.csdn.net/dark_cy/article/details/886987364 o' t8 ]. B2 Y5 }
    9 Q# F% H6 X5 Z& |) h# a1 ~+ I
    * l" F) T$ _7 O9 M5 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, 2026-7-30 01:24 , Processed in 0.305709 second(s), 59 queries .

    回顶部