QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 2705|回复: 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算法—终于全部弄懂了
    ) A; Y; a1 B# F3 U! o. J) t6 x简介
    * s. J) T& c" m6 F( Z4 N  KMP 算法是 D.E.Knuth、J,H,Morris 和 V.R.Pratt 三位神人共同提出的,称之为 Knuth-Morria-Pratt 算法,简称 KMP 算法。该算法相对于 Brute-Force(暴力)算法有比较大的改进,主要是消除了主串指针的回溯,从而使算法效率有了某种程度的提高。
    - _5 t! L5 l$ G! f" E4 n3 o
    , e4 w! g! k/ @" f& j/ S

    - _7 `9 c6 N+ w* L# k提取加速匹配的信息7 Y) u9 P- [* q/ H' U  l8 e  \
      上面说道 KMP 算法主要是通过消除主串指针的回溯来提高匹配的效率的,那么,它是则呢样来消除回溯的呢?就是因为它提取并运用了加速匹配的信息!
    - ~5 q% r; ?4 i, a+ c5 i3 q. [' j; v: I  这种信息就是对于每模式串 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 `2 a" Q& X5 D& L
      ; }) \! J# M; X
      加速信息,即数组 next 的提取是整个 KMP 算法中最核心的部分,弄懂了 next 的求解方法,也就弄懂了 KMP 算法的十之七八了,但是不巧的是这部分代码恰恰是最不容易弄懂的……( O( {  M. [6 y* S3 [6 C
      1 L! K6 y9 q& O4 \1 \
    先上代码9 \! @& @3 O# c6 U  o% D- Q3 @
    ( x: T+ ^6 D* G7 X
    + q  d3 W, T9 {* ?. c/ T, U
    void Getnext(int next[],String t)
    . a$ C: C* t2 a/ S{
    / v$ s" {$ C: n/ t/ _2 }. w, g) B   int j=0,k=-1;  Q' Q/ `/ `1 H) J$ g7 z$ i
       next[0]=-1;" Z4 v$ d3 e$ k1 C) ~% h
       while(j<t.length-1)
    9 z. ]+ E9 y9 {; V9 Z' G8 ?   {2 E6 K' H" q+ O
          if(k == -1 || t[j] == t[k])+ }1 y6 v, E: v1 N1 j$ M
          {
    , F: R5 [" h3 u2 q0 X. U2 Z/ m         j++;k++;
    ! ~: Z( G8 v2 J2 _         next[j] = k;2 B% i/ e- y. e- y/ {
          }
    ( r$ [* L! g9 j  R2 L$ `/ [      else k = next[k];//此语句是这段代码最反人类的地方,如果你一下子就能看懂,那么请允许我称呼你一声大神!" m! ?: b( g. g+ t& t
       }
    " m+ v" M/ d1 d3 d}. Z" X7 N" y2 G  q2 ]
    , x7 m$ ~& t  V; I6 ^; y" u
    ok,下面咱们分三种情况来讲 next 的求解过程8 a1 a) q; Q: z9 }* M

    4 N; K( i- K- ]3 F2 \. Z. o$ ]
    . c, o  G; w3 j5 Q. f8 Q* ?
    特殊情况& a6 d6 b) ^- q5 F0 O8 o
    当 j 的值为 0 或 1 的时候,它们的 k 值都为 0,即 next[0] = 0、next[1] =0。但是为了后面 k 值计算的方便,我们将 next[0] 的值设置成 -1。1 ]* G6 s: f+ H' Z
    0 x$ J. J2 z3 u' ~" R
    : A0 b' W8 B1 H  G( O. S
    当 t[j] == t[k] 的情况9 g  u" z' {3 r/ h
    举个栗子
    ! L) \* A; X; b 1111.png / Y& T/ n# s% s) S/ |$ |) 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。( z' \* {0 r2 M# m# z4 {
    8 v3 R: o6 z) ?7 e# N
    3 W; G5 j. f) U# P8 |
    当t[j] != t[k] 的情况5 q4 p  c7 `4 G+ G. R5 ?
    关于这种情况,在代码中的描述就是“简单”的一句 k = next[k];。我当时看了之后,感觉有点蒙,于是就去翻《数据结构教程》。但是这本书里,对于这行代码的解释只有三个字:k 回退…!于是我从“有点蒙”的状态升级到了“很蒙蔽”的状态,我心想,k 回退?我当然知道这是 k 退回,但是它为什么要会退到 next[k] 的位置?为什么不是回退到k-1???巴拉巴拉巴拉…此处省略一万字。, `: Q- C+ H+ _/ @

    : m8 M9 ]2 s% r5 l' `

    / o- \( W& U- I+ e我绞尽脑汁,仍是不得其解。于是我就去问度娘…' {  V8 C) a/ ?* B0 e1 t
    在我看了众多博客之后,终于有了一种拨云见日的感觉,看下图
    6 T2 |9 n1 l& g9 }& S" e 2222.png
    4 q! C( x. V: r% B# ]   由第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。& }2 C* Y* I/ w# T! W. x- x$ {
    3 y, q0 S, [9 @" `, U. y$ K5 i8 l) Z
    1 G/ w' f0 C: s  ^+ n
    至此,算是把求解数组 next 的算法弄清楚了(其实是,终于把 k = next[k] 弄懂了…)! E/ U3 n1 n$ T4 Q+ t; ?0 O
    8 d3 L9 l9 O% l, `! q
    # O$ c9 A7 L3 h
    因为这个算法神奇难解之处就在k=next[k]这一处的理解上,网上解析的非常之多,有的就是例证,举例子按代码走流程,走出结果了,跟肉眼看的一致,就认为解释了为什么k=next[k];很少有看到解释的非常清楚的,或者有,但我没有仔细和耐心看下去。我一般扫一眼,就大概知道这个解析是否能说的通。仔细想了三天,搞的千转百折,山重水复,一头雾气缭绕的。搞懂以后又觉得确实简单,但是绕人,烧脑。  r. l4 E; R- x' e. ~# {$ [6 s7 e
    5 F7 b; N9 R. p, E  Q
    & }; B, ~' o: ^; j; F( {1 F
    再此特别感谢昵称为“sofu6”的博客园主,正是他的博客,让我这愚笨的脑袋瓜开窍了( D; Z7 \2 R& a& Q1 v
    * Y7 `* [0 q4 }2 Z* M
    2 `6 U/ r" z5 Q! H
    KMP算法实现" i- B* ~( Z+ x) s# @2 h$ ?
    当你求出了 next 数组之后,KMP 算法就很轻易搞定了,下面我用三张图,让你明白 KMP 算法完成匹配的整个过程。& U4 o0 m/ Z- ^  }; `) m) S1 x; G' T
    以目标串:s,指针为 i ;模式串:t 指针为 j ; 为例
    4 B  r4 M- W4 W! ^- o 3333.png 1 i. H* m( v! ^
    上图表示:“si-j ~ si-1” == “t 0 ~ t j-1”,s i != t j(前面都相等,但比较到 t j 时发现不相等了)且next[j] == k。
    / C/ f+ }" F' k 4444.png 5 B/ F& y8 [( e* U# C
    根据 next 数组的定义得知 “t k ~ t j-1” == “t 0 ~ t k-1”,所以 “t 0 ~ t k-1” == “si-k ~ si-1”
    ! Y1 r1 k/ ]( s$ }, p. @# o) N. V0 Y 555.png % Q- q- o( `* P, K
    将模式串右移,得到上图,这样就避免了目标穿的指针回溯。; _. M% L! G8 \* G9 I; K

    8 Q% g, j( l7 m/ w& b
    . U) d6 {. T) ~: x# g' K2 `
    都明了之后就可以手写 KMP 的代码了- A! r) W! e$ |- P0 a' Z7 {
    6 I; Z" r1 c2 k- b8 N7 T
    / K% [  S8 h) F" x7 v% \1 J
    int KMP(String s,String t)5 Z0 _+ d8 p* a% W
    {' T( s6 Q1 P2 n6 C0 w$ K
       int next[MaxSize],i=0;j=0;
    7 ^/ D: l7 P) p: @- E   Getnext(t,next);+ V5 J/ I$ W  o
       while(i<s.length&&j<t.length)- Z# |/ E3 Q5 M5 x% H: F+ c, [
       {% W+ c' t/ V* l/ h% Z* |
          if(j==-1 || s==t[j]); c$ f) i9 ?8 B& _
          {) B# \7 c, r. Q$ Z) m
             i++;; q& ?! o  y: A  P+ G) o" h6 y
             j++;0 ~- v7 [* o0 \/ t& w
          }
    ' ~5 n7 X4 h5 d) T      else j=next[j];               //j回退。。。
    5 f) ^7 j$ h7 D3 G# [* }) w   }6 }* _# M9 K8 I+ Z+ P, R
       if(j>=t.length)
    9 a. }: u* ]% M$ y& S2 ?4 N       return (i-t.length);         //匹配成功,返回子串的位置' N4 N. d2 M8 O
       else
    ) p8 }$ G) X" ^3 u      return (-1);                  //没找到
    / ~8 n9 i* s: G' ?}$ K4 a4 s( L$ u1 Z, O
    # R6 T: B; [# I) Z* J
    改进后的 next 求解方法
    ' ^# q% t  a# c1 C1 ^先来看一下上面算法存在的缺陷:
    * Q' E$ d6 L6 V$ T; Z. {1 r, h: u 6666.png
    ) {/ N9 K) K- t' N6 Z4 v7 D/ P显然,当我们上边的算法得到的next数组应该是[ -1,0,0,1 ]" E1 `9 E4 `7 j- f# D( i7 Q! X

    + b6 Q! j/ }. m1 @
    2 K) o! w) J3 p. f
    所以下一步我们应该是把j移动到第1个元素咯:
    : e) j0 u6 G& p& v 7777.png " @3 R$ [+ N$ p& w4 f
    不难发现,这一步是完全没有意义的。因为后面的B已经不匹配了,那前面的B也一定是不匹配的,同样的情况其实还发生在第2个元素A上。/ O( `5 W- `" M- p

    - Z) W0 x! A1 c, H
    0 ]7 G" v' L. i, _; F
    显然,发生问题的原因在于t[j] == t[next[j]]。
    0 \6 y! u4 a$ P2 K/ |' J* r
    ' m( u$ B0 @3 P! O1 h

    , d: O( P% j2 R& w  u5 d所以我们需要谈价一个判断:! m" O( T4 }% H

    3 U! A7 q& j4 \' g0 ~; n  W: |! B' Q4 T* X
    " f# J  x4 k7 X* S: H3 p- E( K
    void Getnext(int next[],String t)
    : p% J5 \! t! ?) Y( |{% l2 q7 N& v; s; J- u3 Z. e
       int j=0,k=-1;
    : P/ h* ^+ z8 L) U: ?1 z% X   next[0]=-1;
    ) r% {  n( W. o0 l5 [( n   while(j<t.length-1)0 k5 Q- D6 L& c: H
       {- _1 X  ^6 y& M6 f
          if(k == -1 || t[j] == t[k])
    6 M9 v" v! T2 h' m- ]1 ?, h9 b1 a      {: {) E# J6 t0 C  q( |1 G
             j++;k++;- [: J( R4 @6 L- h9 z
             if(t[j]==t[k])//当两个字符相同时,就跳过1 J' _2 }# A2 L& H
                next[j] = next[k];
    ! E% m4 V* X/ b4 s) t6 E0 d+ T         else
    4 P3 C( E6 X. ~; Z" r/ D            next[j] = k;
    - m1 m* T6 z/ s% v5 g      }
    $ H$ y2 C+ C5 g- n/ i* j      else k = next[k];$ {6 b* `$ P0 w( R- e
       }
    + z7 U% n' q2 v/ M* M8 b2 ]1 f}) X' N2 E; O8 |8 T$ r) o& @
    ————————————————! S$ d, @9 g+ P. |
    版权声明:本文为CSDN博主「June·D」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    . w7 I! b( h4 P3 O4 f$ W原文链接:https://blog.csdn.net/dark_cy/article/details/88698736
    " q! v+ F8 I4 W- B: x6 w( v6 ?2 N" R4 v  W
    " t$ z' O" O$ D* a  S
    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-5-26 18:03 , Processed in 0.367300 second(s), 58 queries .

    回顶部