QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 2782|回复: 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算法—终于全部弄懂了- G& {! y' K) T# _3 m
    简介( u) d* w& y% q7 b# d2 b' Z/ }% ]
      KMP 算法是 D.E.Knuth、J,H,Morris 和 V.R.Pratt 三位神人共同提出的,称之为 Knuth-Morria-Pratt 算法,简称 KMP 算法。该算法相对于 Brute-Force(暴力)算法有比较大的改进,主要是消除了主串指针的回溯,从而使算法效率有了某种程度的提高。
    " w9 P  B* u$ m) E2 e
      ~# h4 Z" U- D+ n/ ~
    8 E' c/ y/ b$ V: M, D
    提取加速匹配的信息# @1 n4 A0 J3 ~8 t! E: f
      上面说道 KMP 算法主要是通过消除主串指针的回溯来提高匹配的效率的,那么,它是则呢样来消除回溯的呢?就是因为它提取并运用了加速匹配的信息!
      ^& c' Q; T- }7 U  h- _; 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 }。
    , ]- H0 E$ g- w8 y* F% h" N  
    ' J; _( W! L3 g9 T" ^0 Z  加速信息,即数组 next 的提取是整个 KMP 算法中最核心的部分,弄懂了 next 的求解方法,也就弄懂了 KMP 算法的十之七八了,但是不巧的是这部分代码恰恰是最不容易弄懂的……
    1 Q5 {8 l* E1 x5 R  % I6 K- O$ }. u7 ~
    先上代码
    ' N0 r3 U! R" u9 Y9 U) A3 P
    1 ?  |4 n. m8 q5 @# |) t( e3 R

    1 k4 A# j) c5 X% o3 s, Jvoid Getnext(int next[],String t)2 O' X' n. Z9 R. A9 O' a
    {
    2 m4 j% W, ?; T; C   int j=0,k=-1;! Q  O2 N9 J$ H# h
       next[0]=-1;
    3 Q  T6 c; q4 }+ F* l6 ?6 G0 t4 R   while(j<t.length-1)
    * j7 s3 b3 ?" ]8 F' g# [   {. d" K, r1 ?9 W8 I+ O$ U8 ~* c, s' c# R
          if(k == -1 || t[j] == t[k]); q2 b: }. S: t. V
          {
    / _  m% x9 z9 h  p! `         j++;k++;
    % M0 G0 \$ f4 Y' E+ p         next[j] = k;
    8 d8 q) q; y7 [% ^# l" j3 _3 D      }+ @7 ~5 J# u8 Z5 Y: g
          else k = next[k];//此语句是这段代码最反人类的地方,如果你一下子就能看懂,那么请允许我称呼你一声大神!
    % R* b8 c" l" w( G. b   }) q# J4 r8 y6 [# |  s
    }
    # ^3 J6 E( D- u- H9 C4 D, U. T5 h& d! Q; K& T! Z& C: N6 L+ L/ L& j
    ok,下面咱们分三种情况来讲 next 的求解过程5 A  o4 @1 c: `' z$ E& s
    # }( @) ]4 s3 ]9 ?/ m4 D0 k
    : w! _. d6 O. U7 O* v
    特殊情况2 l& b5 M& S3 R3 }! [1 A
    当 j 的值为 0 或 1 的时候,它们的 k 值都为 0,即 next[0] = 0、next[1] =0。但是为了后面 k 值计算的方便,我们将 next[0] 的值设置成 -1。
      _8 ]. I: C2 }+ T. ~( l4 d, ?" a7 K

    3 ~- ]% V. F$ A  B5 O! u& B0 o当 t[j] == t[k] 的情况
    $ `! |  X; z0 W. P' j0 t2 h举个栗子
    7 Y7 Y; n3 Q8 E! C 1111.png   S9 T" R8 Q4 N1 ]$ W% j+ Y
    观察上图可知,当 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。
    9 z! g! b' [, D& C& a2 t$ x. P
    . m. o4 f6 u# J" |1 e
    : F9 ^( }$ N& b& V' j
    当t[j] != t[k] 的情况
    5 X( W' s) P8 }) w+ J关于这种情况,在代码中的描述就是“简单”的一句 k = next[k];。我当时看了之后,感觉有点蒙,于是就去翻《数据结构教程》。但是这本书里,对于这行代码的解释只有三个字:k 回退…!于是我从“有点蒙”的状态升级到了“很蒙蔽”的状态,我心想,k 回退?我当然知道这是 k 退回,但是它为什么要会退到 next[k] 的位置?为什么不是回退到k-1???巴拉巴拉巴拉…此处省略一万字。
    * ~3 O) h! o* {0 ~
    6 d- _+ o1 T9 I+ {

    9 k% x, m! q1 j' o我绞尽脑汁,仍是不得其解。于是我就去问度娘…6 B0 b$ W6 U" b6 s% c( `% _
    在我看了众多博客之后,终于有了一种拨云见日的感觉,看下图+ i7 Q2 ^' F1 F: i% i( E
    2222.png
    ) ]5 H! p) W  L1 \  e7 r9 ~   由第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。/ P# O3 C5 V* f0 A! Z

    3 ^: H. C$ c* a4 f

    : x4 X* r' G( B4 Z0 [7 h至此,算是把求解数组 next 的算法弄清楚了(其实是,终于把 k = next[k] 弄懂了…)
    ( |6 |8 w2 v* n
      v+ p- \0 O6 [. x4 F- H

    9 d0 p2 I& N; @) X% W/ @6 A$ T因为这个算法神奇难解之处就在k=next[k]这一处的理解上,网上解析的非常之多,有的就是例证,举例子按代码走流程,走出结果了,跟肉眼看的一致,就认为解释了为什么k=next[k];很少有看到解释的非常清楚的,或者有,但我没有仔细和耐心看下去。我一般扫一眼,就大概知道这个解析是否能说的通。仔细想了三天,搞的千转百折,山重水复,一头雾气缭绕的。搞懂以后又觉得确实简单,但是绕人,烧脑。! y, M* C) G* m; h& N8 h( _

    " @6 W$ ~4 k" J7 @( Y
    9 [9 e8 S! }4 O6 o
    再此特别感谢昵称为“sofu6”的博客园主,正是他的博客,让我这愚笨的脑袋瓜开窍了2 V5 I, Z: H9 F  O
    - p2 H" C% T( d& u1 e
    6 n% a: j2 H( i4 g7 w/ d- P1 y* L) Y
    KMP算法实现% S. ]$ |' H0 g& Q* D0 ]3 M/ ^: }
    当你求出了 next 数组之后,KMP 算法就很轻易搞定了,下面我用三张图,让你明白 KMP 算法完成匹配的整个过程。
    ( F  d: n' Z) h& P以目标串:s,指针为 i ;模式串:t 指针为 j ; 为例
    , s3 {# L) `. J. Q" f 3333.png
    & A: m1 C- L! D! j/ Z/ }上图表示:“si-j ~ si-1” == “t 0 ~ t j-1”,s i != t j(前面都相等,但比较到 t j 时发现不相等了)且next[j] == k。5 q/ S& O9 p% R$ C) o
    4444.png 4 ~' e/ `$ l9 c  v' I
    根据 next 数组的定义得知 “t k ~ t j-1” == “t 0 ~ t k-1”,所以 “t 0 ~ t k-1” == “si-k ~ si-1”. e$ b1 z2 @6 j- Q- I: t# V& p' T
    555.png 9 G4 C. b- K/ L6 ]2 U- ?& Z
    将模式串右移,得到上图,这样就避免了目标穿的指针回溯。1 y/ @5 w/ Y9 G& L# Q) ]

    # x, W, J/ n9 ~" ~, w

    0 t% j" @. e* _5 F都明了之后就可以手写 KMP 的代码了
    ' u, W5 Z$ u: D; Y+ K) E/ L( v& F3 O. q# _9 J" f3 P: \
    1 ?( E/ X7 a' h. F  a6 \# U
    int KMP(String s,String t)
    & [+ {, R, u( u1 H: w' ?3 F{* ?0 R4 l% h" ~* B6 r( [5 T
       int next[MaxSize],i=0;j=0;
    9 Z& e# k, U1 y2 E   Getnext(t,next);
    9 D8 ^' I3 T* L   while(i<s.length&&j<t.length)6 D/ U$ k' u- s" l0 ^9 p
       {: `5 ~# H) S) u# r) n6 B' I
          if(j==-1 || s==t[j])
    : ^$ P# h% X# u* ~      {: h3 o/ M& Q# Z$ W$ v+ h" D: X3 J
             i++;2 c- z0 j1 I1 i6 H+ R4 j
             j++;
    9 V2 p# E( J$ @. a  O      }8 b* E; D: j" N; A- c+ K. X
          else j=next[j];               //j回退。。。
    ) \$ Z6 C: x' e. x' h8 d3 L   }: J3 m" E) K  @1 |! U* k
       if(j>=t.length)
    : h" r* ^* J- J4 C       return (i-t.length);         //匹配成功,返回子串的位置
    3 N" q1 @9 S0 L( s" [% O$ I   else
    3 r6 G: X: x3 |- `  E      return (-1);                  //没找到, Y; M7 s* }5 {1 J: Y8 J
    }
    2 I" G& z, \0 ?# n0 Y% q1 l1 ?1 I. w% o6 s, b
    改进后的 next 求解方法/ f( c) o' E; _- `, \- k
    先来看一下上面算法存在的缺陷:' R. R3 B0 N8 M5 _
    6666.png - h& T! P: y' R' K( p( {4 N
    显然,当我们上边的算法得到的next数组应该是[ -1,0,0,1 ]* Z3 m4 O; M) K% b$ I

    " ?# [- W9 g) ~/ Y8 \  I2 }1 p

    8 n8 o" O5 b$ T& `3 a- \2 w所以下一步我们应该是把j移动到第1个元素咯:6 B) E5 L* [0 r+ i/ O+ y( a
    7777.png
    7 C* U/ G: I# ?& z( G不难发现,这一步是完全没有意义的。因为后面的B已经不匹配了,那前面的B也一定是不匹配的,同样的情况其实还发生在第2个元素A上。0 ^: C7 Q1 [# h) w) K; E
    * j( G/ L9 A3 [: V

    , V+ Q) K9 j; ^: W- X0 m6 ^显然,发生问题的原因在于t[j] == t[next[j]]。
    5 i4 O' w; Q# \& P& t5 k  X) K5 F: b; K/ B/ C# |. @! @- i

    3 e& P3 P, Q5 a8 G. Q: B  m所以我们需要谈价一个判断:
    - c, ]+ i' U) X, X2 Y4 K6 l2 G
    6 n2 G3 k, ]/ t% c9 U2 O+ j7 y

    ' m/ Z6 W7 A2 a" {, Tvoid Getnext(int next[],String t)
    - |2 L- z% m  P( u9 `{2 Q* {3 Z/ O' B. M
       int j=0,k=-1;
    0 V, ^1 z, ]1 u+ z   next[0]=-1;
    0 M! R0 ^/ x" g0 X+ \   while(j<t.length-1)3 Q5 M3 }: |& T- h# R! H, n
       {: f7 ]2 x6 q1 V9 s: c2 S, o1 Q* ]
          if(k == -1 || t[j] == t[k])& w: `8 f( d. U# S3 M
          {
    ) n2 T# }4 {& d( V         j++;k++;* h1 C3 P: W$ L5 Q
             if(t[j]==t[k])//当两个字符相同时,就跳过
    & v# k* ?1 P9 ~* y            next[j] = next[k];) k: Z/ v* }. o- z9 D, ~2 w
             else
    0 N0 d! L4 K9 L            next[j] = k;
    & A; N0 C2 x% g      }
    . m" X2 Y! i8 e      else k = next[k];
    ) V. e7 A2 |4 A7 D. V* M   }
    5 j# H/ X1 J8 F& |* S}
    1 ~5 \  w8 g- h$ Q, ^# e0 k————————————————
    ! G% l" i* ~  u7 a9 I1 d版权声明:本文为CSDN博主「June·D」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。" p, B" ?6 T3 i+ N6 l- y4 V: S/ [8 f
    原文链接:https://blog.csdn.net/dark_cy/article/details/88698736) c, v6 z7 o9 {) y" V
    6 M1 P0 t/ M0 U3 D  y8 x
    & _9 `$ F/ T; w; \0 d, A
    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-9-13 22:29 , Processed in 0.373522 second(s), 59 queries .

    回顶部