QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 2754|回复: 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算法—终于全部弄懂了
    6 L; F8 d' m+ {% Y' n* L简介
    3 W( P1 [& E$ h( H/ U/ D" x  KMP 算法是 D.E.Knuth、J,H,Morris 和 V.R.Pratt 三位神人共同提出的,称之为 Knuth-Morria-Pratt 算法,简称 KMP 算法。该算法相对于 Brute-Force(暴力)算法有比较大的改进,主要是消除了主串指针的回溯,从而使算法效率有了某种程度的提高。
    ! q% w: ]1 S$ q: E* E7 f9 B6 y4 J' N5 e+ D0 ]9 G/ [

    8 N0 X* C  d8 ~# t! p( b5 A2 n提取加速匹配的信息
    & C- u- }7 |: G  S  上面说道 KMP 算法主要是通过消除主串指针的回溯来提高匹配的效率的,那么,它是则呢样来消除回溯的呢?就是因为它提取并运用了加速匹配的信息!/ ~. n- a3 P: F0 a: k4 u/ l
      这种信息就是对于每模式串 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 }。! P  S. }2 b  y7 q" \+ E, O# ~
      6 h+ \% p7 p5 T6 W* x3 ~2 |5 _
      加速信息,即数组 next 的提取是整个 KMP 算法中最核心的部分,弄懂了 next 的求解方法,也就弄懂了 KMP 算法的十之七八了,但是不巧的是这部分代码恰恰是最不容易弄懂的……, k: C0 X/ p+ V  L% U
      5 \4 B' L! B/ c3 |+ V0 W4 Z
    先上代码
    % _8 B" F# A2 @/ U" r6 O# P+ N' ~4 R% `% f, X
    + M# d$ t+ m3 U  B% ?! {6 Y$ t
    void Getnext(int next[],String t)
    0 L/ q, A& J1 u" e, A, o3 Z; S{/ o& R& K/ Z1 ]( {: f5 J; ]
       int j=0,k=-1;
    0 L! Z7 P; G2 X$ _   next[0]=-1;
    3 e& q, f: M( X: \) u7 V   while(j<t.length-1)
    % C% l; b1 p; {, c: j   {+ D4 l8 r6 X6 {  ?( O7 B
          if(k == -1 || t[j] == t[k])
    5 n0 f9 L" f8 K6 K8 e& g      {
    8 W% s+ t1 Z/ O# K         j++;k++;
    ( y6 y6 C3 E, S4 F. @- {9 z" v/ P* G         next[j] = k;
    - [2 I  C0 P) N' \      }4 ]8 R4 K( \- R1 e. n$ I
          else k = next[k];//此语句是这段代码最反人类的地方,如果你一下子就能看懂,那么请允许我称呼你一声大神!( l4 [: d6 E% h3 u
       }
    ! f3 \' i% C) T}( f, `* Z; B6 b
    , V. F+ Y9 Q- [" y2 T7 x8 r
    ok,下面咱们分三种情况来讲 next 的求解过程5 F# A, I" x- S9 l  u0 p- H
    - z* [  v2 ]9 ^6 v6 u, [) [  e

    5 E/ m% m# D  `5 @1 S/ m特殊情况% }. j2 P4 l5 a4 U: w, y
    当 j 的值为 0 或 1 的时候,它们的 k 值都为 0,即 next[0] = 0、next[1] =0。但是为了后面 k 值计算的方便,我们将 next[0] 的值设置成 -1。1 e  W2 `+ H/ s# C/ i

    7 A& B- X9 A( [  Y$ G

    % S9 s+ z0 @# J- O' M, U& b当 t[j] == t[k] 的情况
    / ?- Y! y/ ~+ `$ g: `' U举个栗子" e" F0 `8 j- P
    1111.png 7 ?# v  r- [0 M
    观察上图可知,当 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 t% f& k7 ?+ g  x) k

    , ~; ], [4 B3 p1 t5 P
    ( u0 d" Y% j8 |$ J* L  F! @
    当t[j] != t[k] 的情况+ a2 X& i( F  w* C& Z" N
    关于这种情况,在代码中的描述就是“简单”的一句 k = next[k];。我当时看了之后,感觉有点蒙,于是就去翻《数据结构教程》。但是这本书里,对于这行代码的解释只有三个字:k 回退…!于是我从“有点蒙”的状态升级到了“很蒙蔽”的状态,我心想,k 回退?我当然知道这是 k 退回,但是它为什么要会退到 next[k] 的位置?为什么不是回退到k-1???巴拉巴拉巴拉…此处省略一万字。# ]) J# ^8 d2 z' {( f7 ]* D
    5 h: ^! q5 c  B
    0 B* U1 |4 p, g/ v1 O
    我绞尽脑汁,仍是不得其解。于是我就去问度娘…1 g7 u8 T7 V- P- k" _
    在我看了众多博客之后,终于有了一种拨云见日的感觉,看下图9 {$ X$ `# u. V- C
    2222.png
    5 Z. L0 W5 T1 k0 b8 O+ G% m$ c   由第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% q: b' r5 X: ^

    & X0 z1 q2 j8 J
    9 K3 Q+ Q/ G3 |
    至此,算是把求解数组 next 的算法弄清楚了(其实是,终于把 k = next[k] 弄懂了…)5 {7 L$ p1 M  Q$ g3 X2 S
    , G2 N8 c" A" n( x) H
    , G; j- }# O$ i8 J! h
    因为这个算法神奇难解之处就在k=next[k]这一处的理解上,网上解析的非常之多,有的就是例证,举例子按代码走流程,走出结果了,跟肉眼看的一致,就认为解释了为什么k=next[k];很少有看到解释的非常清楚的,或者有,但我没有仔细和耐心看下去。我一般扫一眼,就大概知道这个解析是否能说的通。仔细想了三天,搞的千转百折,山重水复,一头雾气缭绕的。搞懂以后又觉得确实简单,但是绕人,烧脑。
    ! u% ?% L3 _2 U0 o
    ( k( b. `( x! @* _: S' f% ^
    & O3 E  M( S% \$ f; Y: }# {
    再此特别感谢昵称为“sofu6”的博客园主,正是他的博客,让我这愚笨的脑袋瓜开窍了5 ]1 H- [: a( l

    " U2 q1 f+ m7 }" A& c# R& c$ s1 F

    6 q) T$ H* I* p) g* jKMP算法实现; t  H( B0 d* z8 [3 J2 w
    当你求出了 next 数组之后,KMP 算法就很轻易搞定了,下面我用三张图,让你明白 KMP 算法完成匹配的整个过程。
    4 v4 f/ Y. b8 b8 V; i$ I以目标串:s,指针为 i ;模式串:t 指针为 j ; 为例, l5 `1 w! t* B( J( G! k$ L
    3333.png : a* ^8 o# i& o4 I4 |/ l' i2 r  j& C
    上图表示:“si-j ~ si-1” == “t 0 ~ t j-1”,s i != t j(前面都相等,但比较到 t j 时发现不相等了)且next[j] == k。5 M1 k# e6 [% F. V) Z" z
    4444.png 0 ?! x7 z% _. l; e" q2 d* B7 ]
    根据 next 数组的定义得知 “t k ~ t j-1” == “t 0 ~ t k-1”,所以 “t 0 ~ t k-1” == “si-k ~ si-1”. u4 w; A5 B: j% b3 a
    555.png
    : L" _* d! U% K! g将模式串右移,得到上图,这样就避免了目标穿的指针回溯。& G% p- P4 H4 t& R; g3 n4 b; s
    ! c% E, u" y' }0 y6 t* }8 ]
    ) P! R/ E3 c. o* p5 e
    都明了之后就可以手写 KMP 的代码了9 U2 s$ L/ |* n0 J; L) h5 C$ [# |
    1 w) R' E' R# c

    4 C% {2 u: x8 {4 C) Vint KMP(String s,String t): K+ |3 U8 c& A9 b
    {
    - y& u9 t: B" g$ g   int next[MaxSize],i=0;j=0;2 ]2 C) ^5 u. m0 d/ F- b4 v4 S
       Getnext(t,next);& w4 u- C0 O" V1 w. G+ L  v
       while(i<s.length&&j<t.length)
    0 \) J( w9 P% V* w3 B, R( W6 a   {
    ) z/ W) H3 I( `8 d: i* {$ W      if(j==-1 || s==t[j])
    , Z$ K4 x# X( [' ?- L4 Y# o      {! u" S; i0 n, v+ n, V
             i++;- m. E% @8 K( W* }
             j++;* l' F; a! r. r* T! m4 W- u6 j# v
          }
    ' t! L- f: p. f& c2 d# V- E: e      else j=next[j];               //j回退。。。
    & Q3 Y/ C' u) Q3 |   }1 l7 r% N- {3 n( M9 q
       if(j>=t.length)
    1 t5 C, _: t' S0 C; I       return (i-t.length);         //匹配成功,返回子串的位置
    3 D4 q7 l  S% k) N3 Q/ A. m7 I   else
    $ B3 m; {0 A9 g* r0 N( A6 R- Z6 e      return (-1);                  //没找到/ q$ I" X! T' F3 g1 |+ W
    }' C! Y0 u, R: x

    + p8 f! {- d+ |1 y, L! |0 |改进后的 next 求解方法
    + q& T, x- e0 ~0 E先来看一下上面算法存在的缺陷:
    : D2 B7 K; [4 m3 B5 W6 f 6666.png
    - d; _( n& a# @0 h3 S显然,当我们上边的算法得到的next数组应该是[ -1,0,0,1 ]6 a" J7 d; `' y2 V; I
    , n$ _9 J8 F: P! D

    ! M6 d! S+ L' u# a/ @所以下一步我们应该是把j移动到第1个元素咯:- U! T1 Y0 D: d/ R4 I
    7777.png & ~: f8 K! h( U* q/ }4 |5 X9 X
    不难发现,这一步是完全没有意义的。因为后面的B已经不匹配了,那前面的B也一定是不匹配的,同样的情况其实还发生在第2个元素A上。' x3 u5 `& ?5 s7 [6 p

    ! O7 s2 N6 `* }, V- L

    " ]4 g8 L) ]7 \3 {* ~- q0 u显然,发生问题的原因在于t[j] == t[next[j]]。
    % U' r7 v! y5 y; r% D) _7 T9 j! }* F8 t4 I3 F
      e5 z" I2 _" g9 L+ Q% `: w
    所以我们需要谈价一个判断:- ^5 `; z4 k; Q. Q

    7 F" e" _/ j$ R
    7 `) p+ k/ b( ]: c
    void Getnext(int next[],String t)
    ! C4 k( y- I6 p{
    % M9 Q7 j& }- l2 z   int j=0,k=-1;
    " t. O: C$ n% {( Q) F: g0 L4 i3 [   next[0]=-1;% H  ^# Q8 d# A' W6 O: S
       while(j<t.length-1)
    , U7 K, [" Y+ {   {
    9 I9 u0 ]# K  V. r2 _- ^( X      if(k == -1 || t[j] == t[k])1 S8 h, ^6 q+ E9 W/ T6 P
          {
    0 S, F( `- [0 q) Z9 i+ r! w         j++;k++;" f; X$ j8 H. [) T
             if(t[j]==t[k])//当两个字符相同时,就跳过' U5 p/ _$ q2 l" S
                next[j] = next[k];# k8 [9 b4 x0 {! X  `& E
             else
    # ]8 k3 V/ a) J. |) y            next[j] = k;6 S. a& n# @9 ^# |
          }! F! x1 d5 \9 u: G6 a+ U
          else k = next[k];
    ; P9 O" ^* R6 \0 \, ]# @/ }$ v   }
    ' i9 G& R7 H4 X6 |* x% @}( R6 d' e  ?7 r" C! j
    ————————————————
    8 i' y% j# X/ w$ ]% x版权声明:本文为CSDN博主「June·D」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    9 t: ^5 ?; }9 _% K9 P: W" q# ^原文链接:https://blog.csdn.net/dark_cy/article/details/88698736" U; j$ A3 Y% m  w) [. |! R
    9 C# R* I# v" ?( `2 R, ~* i: j3 [
    # @/ i; w% r# j2 Z  h* b
    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 20:13 , Processed in 0.562102 second(s), 58 queries .

    回顶部