QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 2753|回复: 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算法—终于全部弄懂了* \8 o! `1 U% R! \6 p
    简介
    - G! |( @0 [5 B( H0 h& C. \  KMP 算法是 D.E.Knuth、J,H,Morris 和 V.R.Pratt 三位神人共同提出的,称之为 Knuth-Morria-Pratt 算法,简称 KMP 算法。该算法相对于 Brute-Force(暴力)算法有比较大的改进,主要是消除了主串指针的回溯,从而使算法效率有了某种程度的提高。# F' ?. y9 a8 r; X: t( V* v1 B
    / D+ C' I' _. p! {! z* Z

    , k/ \- v% n  X) M" T! l, E提取加速匹配的信息1 n/ _8 D) ~8 B) r
      上面说道 KMP 算法主要是通过消除主串指针的回溯来提高匹配的效率的,那么,它是则呢样来消除回溯的呢?就是因为它提取并运用了加速匹配的信息!" \( M+ ^4 ^# |
      这种信息就是对于每模式串 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 D1 a' A5 Z& z( |5 E, t
      
    ; M0 Q6 M1 b5 I1 B" h0 f* F  加速信息,即数组 next 的提取是整个 KMP 算法中最核心的部分,弄懂了 next 的求解方法,也就弄懂了 KMP 算法的十之七八了,但是不巧的是这部分代码恰恰是最不容易弄懂的……
    / }* }0 F+ b5 u2 W( ?8 M  
    1 j$ e: p; \( x- V! @9 i4 v! ~+ I先上代码
    9 e, w6 t5 E1 W, z" S
    - D1 x2 H0 s5 F2 \$ D) }

    3 e, |( h8 r+ B: h1 ~void Getnext(int next[],String t)6 T" W6 y* n  o/ ]
    {
    0 S# a6 e0 ]! p. C& T0 V( n  _   int j=0,k=-1;" s0 s5 n2 E" {+ X3 ?& P
       next[0]=-1;. h/ r0 Q' o" D
       while(j<t.length-1)
    / X$ q6 B. z( O" R$ N# c/ I$ v   {0 X; q' |) v$ `! O& p9 |* ]
          if(k == -1 || t[j] == t[k])
    ( `6 J* W$ U9 M4 m2 X      {' _: o# a( e9 G% Z
             j++;k++;1 e2 X7 Q7 B, u! M6 s3 Z
             next[j] = k;. J) Z0 K/ J, a% }" i' R
          }
    . @" n. e* v/ n. d      else k = next[k];//此语句是这段代码最反人类的地方,如果你一下子就能看懂,那么请允许我称呼你一声大神!; H& o8 \5 f7 b& p, F" G: b$ b
       }
    # ~' a  b+ S; I, i- \3 U; a$ }}
    0 G* R3 b$ d" L. M/ F3 B! }
    0 v) Y% [1 |& T: Lok,下面咱们分三种情况来讲 next 的求解过程1 ]: s' ^  s% W7 e9 Q
    " B" a. V1 n4 g0 }( \
    * {  S: q+ R* T2 h6 z
    特殊情况
    # g# w7 A5 n0 d; _: p5 a. J当 j 的值为 0 或 1 的时候,它们的 k 值都为 0,即 next[0] = 0、next[1] =0。但是为了后面 k 值计算的方便,我们将 next[0] 的值设置成 -1。
    3 ^4 n% A* U2 n% h9 j/ M
    5 C+ t* @  M" g& M
    ; _! w) ]9 V: }' G! A2 j3 U, j
    当 t[j] == t[k] 的情况8 `2 }7 E4 d8 C6 W2 Y" v: U) v
    举个栗子
      R8 v! V6 |# e, A$ q 1111.png
    ! `& ~  m+ ]& W: P3 W3 h& v8 |# s. ?观察上图可知,当 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。
    / Y2 y4 m5 l- f; I  }4 k
    : g" i3 I4 E" k7 Y4 d( b

    $ ~5 a% ~' Z. p  |4 Y  P当t[j] != t[k] 的情况! F6 p+ m4 h# N& k5 @, B( h6 y
    关于这种情况,在代码中的描述就是“简单”的一句 k = next[k];。我当时看了之后,感觉有点蒙,于是就去翻《数据结构教程》。但是这本书里,对于这行代码的解释只有三个字:k 回退…!于是我从“有点蒙”的状态升级到了“很蒙蔽”的状态,我心想,k 回退?我当然知道这是 k 退回,但是它为什么要会退到 next[k] 的位置?为什么不是回退到k-1???巴拉巴拉巴拉…此处省略一万字。% P) \: {, W% M1 V; P9 y; U9 v1 k4 ]
    + u8 Y8 W; {% l" T6 q& |  G
    1 b' V% `& J$ f  S; X6 f
    我绞尽脑汁,仍是不得其解。于是我就去问度娘…1 v3 U8 g# T& ?  ]
    在我看了众多博客之后,终于有了一种拨云见日的感觉,看下图
    % v" x  s" I' ^# i5 \ 2222.png ! P! q: Z" |) x, V
       由第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# B# s/ N1 m5 p' e+ R  s

    + ]7 |' L, M0 Q, U+ ~
    # T7 y" J2 T5 q2 t4 K3 A6 ~
    至此,算是把求解数组 next 的算法弄清楚了(其实是,终于把 k = next[k] 弄懂了…)
    * I9 A  k' |1 y0 H: j; M
    5 Y+ m* ~* W+ D) l3 A$ X/ p2 N, U

    # Y6 @* x( h2 v3 p$ y5 l0 P因为这个算法神奇难解之处就在k=next[k]这一处的理解上,网上解析的非常之多,有的就是例证,举例子按代码走流程,走出结果了,跟肉眼看的一致,就认为解释了为什么k=next[k];很少有看到解释的非常清楚的,或者有,但我没有仔细和耐心看下去。我一般扫一眼,就大概知道这个解析是否能说的通。仔细想了三天,搞的千转百折,山重水复,一头雾气缭绕的。搞懂以后又觉得确实简单,但是绕人,烧脑。1 b( n+ R. U+ s) F: v1 C
    4 [1 \4 l  R) T! t0 G+ E: J( X
    $ D" `; _5 D9 y8 T/ X
    再此特别感谢昵称为“sofu6”的博客园主,正是他的博客,让我这愚笨的脑袋瓜开窍了) y2 o  v3 t* U2 `
    4 T3 M- N2 m% l0 L* t, O
    " N. x' I% H0 C7 l# w% [5 ~5 s( I1 Z4 y
    KMP算法实现
    ' ?! K$ _7 x) D) e9 C当你求出了 next 数组之后,KMP 算法就很轻易搞定了,下面我用三张图,让你明白 KMP 算法完成匹配的整个过程。
    , d4 N# R7 D7 O7 u% F( n以目标串:s,指针为 i ;模式串:t 指针为 j ; 为例* n; r2 X7 u7 ?6 Q
    3333.png 6 k- ?, `0 `. Q
    上图表示:“si-j ~ si-1” == “t 0 ~ t j-1”,s i != t j(前面都相等,但比较到 t j 时发现不相等了)且next[j] == k。
    & n) W) G+ f+ p 4444.png 5 d( x- W, s$ V2 m
    根据 next 数组的定义得知 “t k ~ t j-1” == “t 0 ~ t k-1”,所以 “t 0 ~ t k-1” == “si-k ~ si-1”
    6 H6 d' D% T* o+ F- g$ m 555.png 0 q' l# l% p+ a& k
    将模式串右移,得到上图,这样就避免了目标穿的指针回溯。
    4 i0 y) z* V: q. Y$ {* y2 s+ }* d$ [  c* ?8 L# k% C6 J' \2 f5 ^
    8 E! b7 ^5 V# B  J8 r: `; h
    都明了之后就可以手写 KMP 的代码了
    4 v+ F( H1 R' v, I% N9 V: u+ x
    ' D5 Z( [8 k0 ?& w
    ' l( b4 u8 |4 d* p+ }" b
    int KMP(String s,String t)% f1 }, f0 n+ l/ {' T/ c# j
    {3 r$ Q6 }# H$ }. j- P0 \% Y
       int next[MaxSize],i=0;j=0;  K2 `; Q4 R+ \+ u, [, p+ v1 H
       Getnext(t,next);7 a, q8 w) N  |  E
       while(i<s.length&&j<t.length)
    + L6 u" _" Z8 u$ [" b0 f4 ~   {, o5 K: |7 o# t2 d1 E0 B6 r
          if(j==-1 || s==t[j])
    8 E! C  O$ H: A! [$ A$ H6 d( h      {
    ) H+ X- h4 A9 {. o' b         i++;
    " v* P5 G$ R+ v+ c/ i         j++;
    8 I: n3 p$ N1 w5 f' `: H: ~! a      }
    ; h& f* }+ G0 L8 x6 h1 }0 E* Y2 L; `      else j=next[j];               //j回退。。。
    & H+ F* {; l- a$ P   }$ @+ K+ u3 M4 m# v
       if(j>=t.length)
    9 S9 b$ b- w, A* o7 |8 V       return (i-t.length);         //匹配成功,返回子串的位置
    6 D3 c5 X, H, Z2 R5 P# T; W   else
    & t+ T+ m! m1 O3 X( a: q5 a      return (-1);                  //没找到
    % U0 C- }) O( G" j2 A4 M: a8 R; ^% u}8 i8 L- I. x  w/ `& |* z
    % v# C( b$ P# ]
    改进后的 next 求解方法) i( [/ w  w6 m3 V' B9 U& x5 w$ `
    先来看一下上面算法存在的缺陷:* {  t! Y) b. M" f, b
    6666.png
    3 c# Y( `1 B: Q" E) n3 W& u4 G8 I显然,当我们上边的算法得到的next数组应该是[ -1,0,0,1 ]- @9 Y' D6 A2 h# U$ q/ h* e
    ( C/ z5 O& _: p9 j$ Q  O
    6 N1 G. M# X, S
    所以下一步我们应该是把j移动到第1个元素咯:
    6 t: C+ ]6 G6 h) h 7777.png 7 e1 H9 P. y1 X0 Q: f
    不难发现,这一步是完全没有意义的。因为后面的B已经不匹配了,那前面的B也一定是不匹配的,同样的情况其实还发生在第2个元素A上。
    ( Z  @4 Y! q5 B# l3 R9 M( y2 K/ l- F" G+ z' f8 g* j5 p' I
    ) s0 Y6 [- v! k; S- p
    显然,发生问题的原因在于t[j] == t[next[j]]。# J# L& u8 ^6 o4 @

    * |+ x$ x6 [* y4 T
    1 Z' T! b2 I6 J2 ^/ p: s
    所以我们需要谈价一个判断:6 k! t7 G6 d% B8 q5 `% ]

    . p. X9 U) w6 N4 h0 d- ]5 y

    & g( |. W9 \3 Gvoid Getnext(int next[],String t)/ G6 q" Z$ n3 C% Z$ @8 w: q
    {
    4 h2 v2 ~9 }& f  f% z   int j=0,k=-1;3 O$ V8 ~( u6 T) f1 u! X
       next[0]=-1;
    - D* f8 }' l0 e% }5 R9 K   while(j<t.length-1)( Q+ I+ g( P0 m; z, f) B
       {/ o6 i( a: _* j/ C* T" r5 U2 A
          if(k == -1 || t[j] == t[k]), y6 e7 V2 R3 o7 n/ V
          {
    $ X7 M% E- [  p* Y         j++;k++;
    0 Y- ~8 I1 u: J1 w- d0 v         if(t[j]==t[k])//当两个字符相同时,就跳过3 q# W8 Y% ^( ]% A  A
                next[j] = next[k];  j* g/ Z- N# a- Z% Q
             else
    7 o: f; o" H% l( L3 q1 D  Z7 n            next[j] = k;9 Q- I; e5 R5 g% u3 m
          }1 ^2 q' C7 O* e! ?
          else k = next[k];
    3 H1 U5 ?" b9 u9 d, u   }
    - @/ J. B' `2 [* V5 u# G3 t}3 V" S9 m% b# y3 m+ ^
    ————————————————% ^: h8 ]! M, _4 N1 ^
    版权声明:本文为CSDN博主「June·D」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    8 ^8 s+ D4 l0 ]" Q8 I* ~, R5 s原文链接:https://blog.csdn.net/dark_cy/article/details/88698736# c& ?' T1 x7 V- n; Q3 Y' D2 n

    ( q& T0 L$ }# S  U
    - H  y( W' F/ e# r5 o0 D
    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-28 19:11 , Processed in 0.349943 second(s), 59 queries .

    回顶部