QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 2431|回复: 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算法—终于全部弄懂了
    ( ~0 j/ f! m& J! U简介2 H0 x  g  U/ x# k5 X  O; R- |9 B5 S7 m
      KMP 算法是 D.E.Knuth、J,H,Morris 和 V.R.Pratt 三位神人共同提出的,称之为 Knuth-Morria-Pratt 算法,简称 KMP 算法。该算法相对于 Brute-Force(暴力)算法有比较大的改进,主要是消除了主串指针的回溯,从而使算法效率有了某种程度的提高。
    ) R5 [& ?2 ~7 _0 Z3 p$ b. D  r; j0 Z
    2 Z  R5 x+ y& d/ Q1 k
    8 W/ M2 C2 j; V# Y/ f
    提取加速匹配的信息
    4 y$ o) e8 B4 ]9 P  上面说道 KMP 算法主要是通过消除主串指针的回溯来提高匹配的效率的,那么,它是则呢样来消除回溯的呢?就是因为它提取并运用了加速匹配的信息!) b2 n2 X; Z$ E3 b5 n
      这种信息就是对于每模式串 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 }。
    - j" z" x8 V: }3 o! w2 ~/ e7 |  
    0 {/ e8 r; n0 J0 b. R  加速信息,即数组 next 的提取是整个 KMP 算法中最核心的部分,弄懂了 next 的求解方法,也就弄懂了 KMP 算法的十之七八了,但是不巧的是这部分代码恰恰是最不容易弄懂的……. @8 _3 o( L( N# v7 f/ j' t
      ( i6 V/ i6 [# d# l  D, C
    先上代码
    / ?/ O5 l% p2 X5 x5 d, E; S5 j; B1 H; c% @3 i6 H6 ^, j4 s

    / `6 n; J" C/ G5 Ovoid Getnext(int next[],String t)" A9 z/ \+ f! ~% [* D# g
    {
    ) f6 b+ T- g& C" B2 v  _7 k   int j=0,k=-1;
    4 i' H6 f# }3 b- ^0 O$ \- I% n   next[0]=-1;$ H. S- _% T0 _6 w$ }  J
       while(j<t.length-1)! I  K, \: `: U+ {
       {
    - }. \+ g4 M+ }+ P- }7 n2 ?0 }, j' G      if(k == -1 || t[j] == t[k])6 m1 P1 w9 _0 j7 ^+ r2 O" a! d) j
          {
    4 _, s7 i9 M! U; x. t         j++;k++;  O) T# S8 E6 c; n
             next[j] = k;
    6 a  e2 I+ B1 w- N5 p6 S. O      }
    $ F% X: ?/ Z; P, ^      else k = next[k];//此语句是这段代码最反人类的地方,如果你一下子就能看懂,那么请允许我称呼你一声大神!
    . G0 C7 c, Q8 ~: }   }
    $ ]4 ?" E9 f$ G- n1 E/ r}
    3 N% W: J! P9 b# i  J3 E0 e$ [
    3 h7 }! g. Q5 R) Uok,下面咱们分三种情况来讲 next 的求解过程
    9 b1 K4 i) _" H) P$ I* ~! ~) j+ c4 y2 [) q9 Q/ v% F* G% j

    8 i& m' J, P' a6 O0 j0 f特殊情况5 R, _% E$ L: ?7 `
    当 j 的值为 0 或 1 的时候,它们的 k 值都为 0,即 next[0] = 0、next[1] =0。但是为了后面 k 值计算的方便,我们将 next[0] 的值设置成 -1。0 _. r$ e) T& i. W4 p

    0 V' j6 ~2 R( L) ^6 J
    $ m) P  C3 u2 I
    当 t[j] == t[k] 的情况
    $ p2 l: i$ J! [* m: ]5 A+ p举个栗子4 \% Z! D6 E, F2 a
    1111.png ! v, I1 b$ t, S# g5 @& e
    观察上图可知,当 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。
    " L, v# T) n' z0 V
    ' }8 w; D9 S" O" a/ A; ?" o

    , [1 x5 g! [/ p7 r: P2 ]9 z- O当t[j] != t[k] 的情况
    . Y9 O- \" c& C关于这种情况,在代码中的描述就是“简单”的一句 k = next[k];。我当时看了之后,感觉有点蒙,于是就去翻《数据结构教程》。但是这本书里,对于这行代码的解释只有三个字:k 回退…!于是我从“有点蒙”的状态升级到了“很蒙蔽”的状态,我心想,k 回退?我当然知道这是 k 退回,但是它为什么要会退到 next[k] 的位置?为什么不是回退到k-1???巴拉巴拉巴拉…此处省略一万字。
    ' W) B6 m/ L! d7 T# b+ ^' Z$ a" g! b# L3 |) u# {6 Z. ~. V

    $ K% J2 |' t, u0 q$ q我绞尽脑汁,仍是不得其解。于是我就去问度娘…/ }3 S( U( I; p" Y# A  {# E
    在我看了众多博客之后,终于有了一种拨云见日的感觉,看下图/ V7 y! q; i8 ^
    2222.png 8 K9 Y7 E( }; ^
       由第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。( Y3 C4 S  T* A- L: L. L
    7 X4 U. K6 y/ a# F& N8 k% n

    8 J8 ]- u9 x( C6 Q4 S至此,算是把求解数组 next 的算法弄清楚了(其实是,终于把 k = next[k] 弄懂了…)5 y# c6 a( P: W3 ^2 j

    3 M4 w- ?( A2 r: l

    ) q; {6 _" |8 ?因为这个算法神奇难解之处就在k=next[k]这一处的理解上,网上解析的非常之多,有的就是例证,举例子按代码走流程,走出结果了,跟肉眼看的一致,就认为解释了为什么k=next[k];很少有看到解释的非常清楚的,或者有,但我没有仔细和耐心看下去。我一般扫一眼,就大概知道这个解析是否能说的通。仔细想了三天,搞的千转百折,山重水复,一头雾气缭绕的。搞懂以后又觉得确实简单,但是绕人,烧脑。5 s: B* z; V$ c5 F

    . P6 f, @- C! [' ^/ ^5 n- n
    7 n9 K! {  S" e( d
    再此特别感谢昵称为“sofu6”的博客园主,正是他的博客,让我这愚笨的脑袋瓜开窍了/ _. J( O9 S! v* `2 t
    $ G# e; i+ \7 G- N9 V" H: w5 j
    , X1 R: d! J$ N+ j9 e
    KMP算法实现
    % Y1 K( F5 Z; O3 m当你求出了 next 数组之后,KMP 算法就很轻易搞定了,下面我用三张图,让你明白 KMP 算法完成匹配的整个过程。, a' _2 z9 M. Q5 V) @6 F
    以目标串:s,指针为 i ;模式串:t 指针为 j ; 为例
    1 l* u  T! o3 _; l 3333.png % o1 z+ T5 m, z! Y! P
    上图表示:“si-j ~ si-1” == “t 0 ~ t j-1”,s i != t j(前面都相等,但比较到 t j 时发现不相等了)且next[j] == k。
    4 H0 y: j3 v6 A& \) y 4444.png + X& [: @9 p; C& v5 @% d9 M
    根据 next 数组的定义得知 “t k ~ t j-1” == “t 0 ~ t k-1”,所以 “t 0 ~ t k-1” == “si-k ~ si-1”2 W6 Q2 [) L0 {: B5 D: x* p& m
    555.png
    ) |7 Y# k# C) b9 O) z' j. z将模式串右移,得到上图,这样就避免了目标穿的指针回溯。
    7 |7 t5 X7 v$ E* |% u3 ~
    8 w3 a* n2 u" n1 y( c

    5 I$ {6 D3 ?6 l都明了之后就可以手写 KMP 的代码了
    7 j: ~7 l; m+ q& a" N) v# u% R( L+ R, _0 h9 m# c

    6 k, K' J; R  l% Kint KMP(String s,String t)" o/ N) Z8 d4 f" L
    {& J# x2 }+ W7 m# ~) Q
       int next[MaxSize],i=0;j=0;
    8 N8 f3 y9 i% T0 T, D   Getnext(t,next);/ o! g0 e) ^) Z! Z6 t& _( s
       while(i<s.length&&j<t.length)* Z) o- u: B( O
       {
    / |) l# ]" d3 F! u( c8 {      if(j==-1 || s==t[j])
    3 X% Z) `$ g4 a( b! _. B. h      {
    0 x3 B+ j9 t2 U0 `7 X2 L         i++;$ G8 N! u  O2 m$ ~5 G
             j++;
    3 _: y! U9 v! B0 z      }
    9 K; `/ f$ w( k3 |* A2 j      else j=next[j];               //j回退。。。
    7 L" ^! w7 J- B% D/ @   }( {! ?# N) p  {8 j, O
       if(j>=t.length)
    ' ^4 c& J7 l. U: l, C       return (i-t.length);         //匹配成功,返回子串的位置* N9 o( a. |+ |% A
       else
    / a+ z- m0 i2 p! {      return (-1);                  //没找到
    & a! ~9 x) t: i0 J" r6 j% M3 p, l}
    . i* w1 Z" i" w+ m& v8 Y9 M; q; Q( S7 e* t; P. k: T" x0 x
    改进后的 next 求解方法
    ' T+ @" B  B5 R/ m: H" h7 b  P先来看一下上面算法存在的缺陷:
    $ ~  G# d/ t: y; X0 E0 O, l" K  A 6666.png 3 j9 ~/ ?  I- W+ u! ]. M" F
    显然,当我们上边的算法得到的next数组应该是[ -1,0,0,1 ]3 D+ L# I" y: {- n! b* F0 z

    # U, w9 C# ?; b( w9 Q! m- g3 N

    1 w. S. i- o! |: N所以下一步我们应该是把j移动到第1个元素咯:
    9 h" }8 U" l+ J* R' F0 J 7777.png
    5 @! u* K2 F- K不难发现,这一步是完全没有意义的。因为后面的B已经不匹配了,那前面的B也一定是不匹配的,同样的情况其实还发生在第2个元素A上。
    * d! I* |) n& P; W, B
    - ?9 d9 E) e; [  y6 U; v
    : e. p/ p% m8 q( j# \- U; t
    显然,发生问题的原因在于t[j] == t[next[j]]。) b6 h! V  \6 u% j) C
    3 D7 |+ l! w) l7 b3 Q/ }+ k
    7 g0 G0 o& t& [8 M& @8 v/ v
    所以我们需要谈价一个判断:
    . S4 x" N6 V# e5 [# ^( F# d. o% U, L/ q9 R

    5 d- j, d' Z( V  s" j3 pvoid Getnext(int next[],String t)
    ' \, a! }- K6 \6 ^' D7 m{/ P/ s; F- A; G# W3 |
       int j=0,k=-1;7 P1 i% p: u! E$ ?
       next[0]=-1;
    2 n3 P+ i, d3 h" G2 h, {   while(j<t.length-1)
    % t" q8 V" q+ o   {
    + w; ]7 s& V3 N4 D) c      if(k == -1 || t[j] == t[k])1 t6 \( U) |0 o$ V1 l' Y1 a+ \
          {
    # r) C: o# j+ @2 w6 r/ _" X8 L, Y3 ]         j++;k++;+ M; |- [$ b' g0 x* i3 B, }, s
             if(t[j]==t[k])//当两个字符相同时,就跳过
    " f/ q! M. [7 W  X8 J) {- r            next[j] = next[k];
    ; T5 Q% q, @  @7 s; S         else
    / v' g) P) g# C6 b/ |  D            next[j] = k;( [* n) ?: _4 t$ T8 f
          }0 K; p7 {5 _7 I
          else k = next[k];) b3 M6 w9 }+ D5 X  O7 F
       }) Q3 X/ \+ p* ~! q( _+ q. I8 d
    }9 _8 U* d; i  M4 [* t, A! ?
    ————————————————* N8 k9 M! ?% T/ K$ x  M, |# \% P
    版权声明:本文为CSDN博主「June·D」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。- H) O; O  D# }: F! R/ Z' O& k8 I
    原文链接:https://blog.csdn.net/dark_cy/article/details/88698736
    - H# |6 [- j: S; q
    - t" g  f6 _( a0 z3 V
    & @0 `) t; E. o- @+ f6 g
    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-7-20 19:56 , Processed in 0.534823 second(s), 58 queries .

    回顶部