QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 2515|回复: 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算法—终于全部弄懂了+ C3 t: e' g+ n9 c; H- Y
    简介3 o. ?) u! n* S0 c
      KMP 算法是 D.E.Knuth、J,H,Morris 和 V.R.Pratt 三位神人共同提出的,称之为 Knuth-Morria-Pratt 算法,简称 KMP 算法。该算法相对于 Brute-Force(暴力)算法有比较大的改进,主要是消除了主串指针的回溯,从而使算法效率有了某种程度的提高。
    / [& d/ j; y( h2 J2 d1 e8 r! c% C9 q
    / P# ^* o! L' u1 C

    0 u& b( ^* ~3 y提取加速匹配的信息
    6 P2 ]8 }6 P! E) J: b" x; T  上面说道 KMP 算法主要是通过消除主串指针的回溯来提高匹配的效率的,那么,它是则呢样来消除回溯的呢?就是因为它提取并运用了加速匹配的信息!
    1 V* O2 j0 T5 b5 o' o$ [  这种信息就是对于每模式串 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 }。) c6 S& C, N; U* U
      + p7 c& w9 {, }4 w% H
      加速信息,即数组 next 的提取是整个 KMP 算法中最核心的部分,弄懂了 next 的求解方法,也就弄懂了 KMP 算法的十之七八了,但是不巧的是这部分代码恰恰是最不容易弄懂的……
    5 o7 d5 z, N1 v" R+ M* ?4 O& o  ' c" C+ W3 {. s# K" P2 U8 ^1 H2 n
    先上代码
    4 z- q& ~- f: T* e8 U! w7 f6 j# s& T3 i5 I; Y$ C% [( x0 k, P) t  {  Z
    + R; P/ o0 L. y) s
    void Getnext(int next[],String t)4 _& j5 `2 r3 X
    {
      U' M/ [" M9 G+ M- `   int j=0,k=-1;
    3 w' V0 P: v5 w   next[0]=-1;
    * x7 e0 Y( ~( Y/ J3 z8 v( w9 E6 V   while(j<t.length-1)- S$ q* u/ ?7 p4 O2 Y+ {
       {7 }  z& U- b0 T  U# ~
          if(k == -1 || t[j] == t[k])5 `. _- c- b) ?4 W$ a+ o9 Z# @
          {* ^. a1 x- R6 O& D
             j++;k++;- \  K: `, W6 N3 C2 n0 T# ~& v2 `7 l+ ?
             next[j] = k;
      w' }, i3 ]) u3 U' k      }
    " R, @9 u, E2 k( d9 x      else k = next[k];//此语句是这段代码最反人类的地方,如果你一下子就能看懂,那么请允许我称呼你一声大神!3 _' e7 g- U' e" r
       }9 D$ `4 c2 v0 D: n/ M, v5 `
    }
    $ r9 A% e3 C. [9 A
    : o8 l- R$ X9 m6 ?, lok,下面咱们分三种情况来讲 next 的求解过程$ e( m& i' V1 I; }* o  e

    * g, ]- z( q/ U

    0 n$ ~6 G: L& _& `$ }$ @特殊情况2 o1 m  j! o+ p9 Z
    当 j 的值为 0 或 1 的时候,它们的 k 值都为 0,即 next[0] = 0、next[1] =0。但是为了后面 k 值计算的方便,我们将 next[0] 的值设置成 -1。
    : L7 o) Y- C, I( U) {; C+ d; s+ x4 K
    * \  Q2 w9 j+ T) Y" I- ~

    0 x6 n# x2 J5 S- F7 E当 t[j] == t[k] 的情况
    3 M# f) w, f: z- G举个栗子5 T9 S# m1 g2 ~) Y; D8 X2 T  P
    1111.png * ?, u* p: T2 @
    观察上图可知,当 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。8 `0 s% N. @( S7 |; z' v* C

    " ^2 Z6 c1 d) K/ i+ {0 V
    : G; H: z9 _9 g- f: a
    当t[j] != t[k] 的情况" W7 P2 C: o* X$ T  k: q1 r7 a
    关于这种情况,在代码中的描述就是“简单”的一句 k = next[k];。我当时看了之后,感觉有点蒙,于是就去翻《数据结构教程》。但是这本书里,对于这行代码的解释只有三个字:k 回退…!于是我从“有点蒙”的状态升级到了“很蒙蔽”的状态,我心想,k 回退?我当然知道这是 k 退回,但是它为什么要会退到 next[k] 的位置?为什么不是回退到k-1???巴拉巴拉巴拉…此处省略一万字。
    - R8 Q( |6 k+ ?' _$ H7 e  S; u+ a4 Q& C) e* a( u% ^* Y3 K

    $ U7 [' R0 `* C2 x2 H我绞尽脑汁,仍是不得其解。于是我就去问度娘…* ~% e9 N, _, k
    在我看了众多博客之后,终于有了一种拨云见日的感觉,看下图
    0 b6 r* B7 @( l) i  V) ^ 2222.png
    8 ?# z3 E' f" A5 Z5 j' |$ R7 j   由第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。
    * d" q7 a0 h, m1 d1 }5 u/ w
    " m/ j8 v( p3 b$ J& X

    " d. w" k& H/ u( }& ?& K至此,算是把求解数组 next 的算法弄清楚了(其实是,终于把 k = next[k] 弄懂了…)8 P5 m% X5 J; G; l8 ?9 i# ~

    % ^* D% E0 b  y9 B* S0 N( j

    * @; P7 M( z6 _% O0 Y! Q1 ^因为这个算法神奇难解之处就在k=next[k]这一处的理解上,网上解析的非常之多,有的就是例证,举例子按代码走流程,走出结果了,跟肉眼看的一致,就认为解释了为什么k=next[k];很少有看到解释的非常清楚的,或者有,但我没有仔细和耐心看下去。我一般扫一眼,就大概知道这个解析是否能说的通。仔细想了三天,搞的千转百折,山重水复,一头雾气缭绕的。搞懂以后又觉得确实简单,但是绕人,烧脑。& V1 m% k$ f6 W% w! U) E
    ( X/ u( a  e: ~+ Y, L+ b! m

    # ^/ P6 {" _0 P3 }2 ^9 n再此特别感谢昵称为“sofu6”的博客园主,正是他的博客,让我这愚笨的脑袋瓜开窍了
    $ |! N. K0 o2 `) f7 u
    ; l( T1 a- I' @3 l
    0 k: i* p  k9 ^" x
    KMP算法实现. X& q4 d2 E4 _* V/ ?
    当你求出了 next 数组之后,KMP 算法就很轻易搞定了,下面我用三张图,让你明白 KMP 算法完成匹配的整个过程。4 P# A& j& x0 v. w
    以目标串:s,指针为 i ;模式串:t 指针为 j ; 为例) m9 n& ^2 ?% K: u: I! u' s' X& L
    3333.png
    , l$ L8 Q2 C2 }- ]; y' M; [上图表示:“si-j ~ si-1” == “t 0 ~ t j-1”,s i != t j(前面都相等,但比较到 t j 时发现不相等了)且next[j] == k。6 E5 E! \- u% X* ?: X) a, ^5 [
    4444.png ) a1 N- s0 {. u5 ^5 ^% n  v8 l9 Q
    根据 next 数组的定义得知 “t k ~ t j-1” == “t 0 ~ t k-1”,所以 “t 0 ~ t k-1” == “si-k ~ si-1”
    6 e5 A$ W- f# k7 K) Z; I 555.png
    . k) c0 ]" `1 a3 L将模式串右移,得到上图,这样就避免了目标穿的指针回溯。
    # M3 R% S) g. w2 P" e; A& d7 T: E% q, i- _, @" L0 A4 l

    " h5 A" W7 F$ s9 j" P4 X0 l都明了之后就可以手写 KMP 的代码了
    ' X, S# ^; }" O. P" N" ~% c7 r1 r8 j: R

    * k7 [0 h9 b! u& C) _+ sint KMP(String s,String t)
    0 [( t: T' ?. J{
    ; I: [  J) B4 h* \; D4 x   int next[MaxSize],i=0;j=0;8 K6 z2 V# U5 e; h7 O: Z
       Getnext(t,next);& |$ u8 G- y4 E4 l" x# S
       while(i<s.length&&j<t.length)* Z/ o3 p0 y+ z6 G
       {9 A/ q0 ^0 w8 J# c: [* y. \
          if(j==-1 || s==t[j])
    9 i7 I$ M! J! c0 O3 I5 @      {8 s. X) [/ B+ g' T% E3 R0 t$ N" m
             i++;
    7 S/ U9 ]0 a& X8 m. y$ p         j++;$ i; V$ |4 y5 X
          }4 b2 Y/ e( S9 \- m
          else j=next[j];               //j回退。。。
    / C* ?8 z& `5 K7 n# p   }3 {5 {7 h% E, }) V% |
       if(j>=t.length)
    ' e4 w7 z( P# {' u6 l! c; t" P8 R: k7 K       return (i-t.length);         //匹配成功,返回子串的位置
      P7 Y6 }$ [  o1 B4 x* F% `   else3 e2 w* P, |) L8 G' a3 P* V# `
          return (-1);                  //没找到
    5 Q* t9 {. v% M& y- ?) f}; x7 ^5 v9 f6 ?
    : w. ^: B8 [1 u0 L4 s6 X, \  I
    改进后的 next 求解方法
    . N7 }3 U7 @1 E' J2 j% \4 A5 I先来看一下上面算法存在的缺陷:: U/ E# D  b, f6 @
    6666.png
    7 A+ i/ I" P- |显然,当我们上边的算法得到的next数组应该是[ -1,0,0,1 ]
    5 v; i4 x; t+ r6 i* T) |
    ) J& _) Z' _! Z" ^3 c, F  i
    6 @. N& j$ w& ^7 \5 @# H2 ?
    所以下一步我们应该是把j移动到第1个元素咯:# [8 _- V% U( |/ V, }' z
    7777.png   Z5 n- v* R* f# {
    不难发现,这一步是完全没有意义的。因为后面的B已经不匹配了,那前面的B也一定是不匹配的,同样的情况其实还发生在第2个元素A上。; h5 J8 A. d2 n( P* W2 p
    3 q# X+ f" q& a
    ' ~8 I. R& T/ _% k, ~
    显然,发生问题的原因在于t[j] == t[next[j]]。
    7 V9 u2 N; V- M& r9 r" E& r) E" W* V/ h. h2 j
    " `  u- I7 A8 l/ `1 M* ?0 P: @
    所以我们需要谈价一个判断:
    ( k4 z- ^; G/ H# Q3 s
    ) r4 ~4 F7 _' F0 V* j. |
    3 B! c! f  k5 \- o8 d' {/ s& y9 L
    void Getnext(int next[],String t)) H" A$ a8 \$ n7 c& k( O
    {
    - x+ `6 i  c# e! L   int j=0,k=-1;
    1 W! r* ~$ I6 G7 e   next[0]=-1;
    6 ~- }5 S- e! `  a* R4 d8 `   while(j<t.length-1)8 c' Q3 @! B: u! f
       {' o+ ]+ r' v4 S. @& n1 F8 f8 u! j
          if(k == -1 || t[j] == t[k])
    0 m; i3 t1 m0 h6 C: U( n: i      {
      ^! m; u8 d. k5 y3 U& A3 H         j++;k++;* U- o8 U& D6 n$ K& i
             if(t[j]==t[k])//当两个字符相同时,就跳过
    ) V+ T9 {7 o6 t            next[j] = next[k];
    - X8 y* y$ F& S& ]9 {. A         else
    * I  e3 h$ j5 @) i/ \4 k* H) A            next[j] = k;
    : o! u  f9 b' v- V: K% j      }
    9 e2 y; ~- @( \      else k = next[k];
    . ]* \; i1 [2 t. |   }
    % x: R7 K( {) G2 G1 b}
    . J- X2 z' ~6 m2 @" r————————————————3 q* q6 V4 w1 ^, i6 @
    版权声明:本文为CSDN博主「June·D」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    3 b5 h7 T  P& ^9 f) f, V5 a原文链接:https://blog.csdn.net/dark_cy/article/details/88698736
    ( f3 _) d* e/ s: l7 Y/ N
    9 u7 d, `8 r* h: X- P0 F6 X3 w- x4 P& o% O6 f) [( m
    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-17 01:19 , Processed in 0.459544 second(s), 58 queries .

    回顶部