QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 2676|回复: 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算法—终于全部弄懂了
    " k6 @4 A( k2 Z4 S7 ?7 b! B简介
    6 y7 C* O* c. J3 O2 Y% j# n( F  KMP 算法是 D.E.Knuth、J,H,Morris 和 V.R.Pratt 三位神人共同提出的,称之为 Knuth-Morria-Pratt 算法,简称 KMP 算法。该算法相对于 Brute-Force(暴力)算法有比较大的改进,主要是消除了主串指针的回溯,从而使算法效率有了某种程度的提高。
    8 ?- A2 R$ H2 N7 E6 ]
    0 [4 o1 K# A1 d3 ~! r; [+ }- d  b
    5 H0 ~+ T4 D6 J6 E+ y0 w
    提取加速匹配的信息# B& Y) ^: y4 S4 P+ t& ~
      上面说道 KMP 算法主要是通过消除主串指针的回溯来提高匹配的效率的,那么,它是则呢样来消除回溯的呢?就是因为它提取并运用了加速匹配的信息!
      Q$ G0 r" p9 s  这种信息就是对于每模式串 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 E: c; r) m; n- z8 M
      4 P: O3 v: O2 O8 o. r
      加速信息,即数组 next 的提取是整个 KMP 算法中最核心的部分,弄懂了 next 的求解方法,也就弄懂了 KMP 算法的十之七八了,但是不巧的是这部分代码恰恰是最不容易弄懂的……
    8 f2 ]& _* q6 \3 e9 o: ~  
    3 [% `7 D# O: E. D$ ^' P, z  ?先上代码7 i( N0 H" W$ |6 B

    + p4 j: l: ?; P& B% O" m% V

    7 ~! Y" Q9 K" \3 u9 M: a9 `1 V- o$ Hvoid Getnext(int next[],String t)
    ) n# l; J; p" y( Y( F; K* ~{
    & L6 u+ c  p) F   int j=0,k=-1;
    * |' Y+ H5 t! S) A   next[0]=-1;* [9 G) z; {! t, c8 D0 j1 H
       while(j<t.length-1)
    $ y- V4 q% @; p: F   {
    # C  P# h+ ]# a& R9 m$ ~      if(k == -1 || t[j] == t[k]); W6 w4 H. n  N$ P4 `' c
          {
    # Z) ~  q* S  t# l% I6 c) F         j++;k++;, H. x2 w! k$ @) ]5 o
             next[j] = k;$ L* @& Z0 Z: |3 |9 {
          }$ O4 f; Z( f. |
          else k = next[k];//此语句是这段代码最反人类的地方,如果你一下子就能看懂,那么请允许我称呼你一声大神!9 g4 v) q5 E9 }4 B$ D; ^
       }
      ~( S3 r- c& y9 j9 T$ l8 R}
      q. l9 Q3 ?/ ^# }9 R+ C' Y# p/ E4 K3 \4 x! ^2 a9 V( j
    ok,下面咱们分三种情况来讲 next 的求解过程# |5 t# h" r5 b; [: w

    ; B" e2 ]; \! O1 m3 `
    2 I. B- h1 @9 L/ f5 D/ O  o
    特殊情况
    & Y, l! G" C" U+ ~1 P- ]当 j 的值为 0 或 1 的时候,它们的 k 值都为 0,即 next[0] = 0、next[1] =0。但是为了后面 k 值计算的方便,我们将 next[0] 的值设置成 -1。
    . p4 t0 ^0 Z) E$ \* j6 M! z3 x" v
    8 ^& C8 d* Y& t
    当 t[j] == t[k] 的情况, O* B/ e/ d, O$ ^  \5 j
    举个栗子
    9 `/ A/ D; M" W" n7 J 1111.png ( r  N, g  Y$ h; f# `4 Z
    观察上图可知,当 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。2 q% ^  O6 a, q' W! d

    0 {# f& }/ N! x, d& ~. [

    # c' D- ~& Y: c当t[j] != t[k] 的情况
    , j; P; v% G+ R/ o7 N关于这种情况,在代码中的描述就是“简单”的一句 k = next[k];。我当时看了之后,感觉有点蒙,于是就去翻《数据结构教程》。但是这本书里,对于这行代码的解释只有三个字:k 回退…!于是我从“有点蒙”的状态升级到了“很蒙蔽”的状态,我心想,k 回退?我当然知道这是 k 退回,但是它为什么要会退到 next[k] 的位置?为什么不是回退到k-1???巴拉巴拉巴拉…此处省略一万字。) ]/ l+ i( d. C# q, t& V! B4 I' t
    ' t& }2 A( g" P0 [

    , e7 j$ v9 [+ k我绞尽脑汁,仍是不得其解。于是我就去问度娘…% E- N% R& ?( h  F- V/ M
    在我看了众多博客之后,终于有了一种拨云见日的感觉,看下图) T4 X4 `6 `& e8 o! A
    2222.png . ^* @& c" f8 o4 g1 k2 C3 Q3 U
       由第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。5 a  N+ m1 n- G% g$ p

      c$ s) j7 S, I# ^9 \6 g
    4 y7 {& B( D/ U' n
    至此,算是把求解数组 next 的算法弄清楚了(其实是,终于把 k = next[k] 弄懂了…)
    1 g/ I4 ?6 t* z$ o$ P! `8 g, ]: B2 Z6 E4 }

    2 Q3 y& ]6 [1 X! ~' m! o  q因为这个算法神奇难解之处就在k=next[k]这一处的理解上,网上解析的非常之多,有的就是例证,举例子按代码走流程,走出结果了,跟肉眼看的一致,就认为解释了为什么k=next[k];很少有看到解释的非常清楚的,或者有,但我没有仔细和耐心看下去。我一般扫一眼,就大概知道这个解析是否能说的通。仔细想了三天,搞的千转百折,山重水复,一头雾气缭绕的。搞懂以后又觉得确实简单,但是绕人,烧脑。& `6 Y3 T6 c) u) y
    ; Z# @' h$ y+ ^+ q/ ]* r' T
    % k  x- ?3 F2 N( S+ W
    再此特别感谢昵称为“sofu6”的博客园主,正是他的博客,让我这愚笨的脑袋瓜开窍了* k7 M( {. v+ O+ k  y

    # p* D* `3 y6 Z. I  R
    " M& M0 b+ }  s7 X) Z
    KMP算法实现' i; l/ l& j0 k; s
    当你求出了 next 数组之后,KMP 算法就很轻易搞定了,下面我用三张图,让你明白 KMP 算法完成匹配的整个过程。
    . {( {# r+ p1 y1 }" `. `以目标串:s,指针为 i ;模式串:t 指针为 j ; 为例  X9 o3 g0 I" u; Y5 i1 T( a- {6 ^
    3333.png
      \5 {2 J# ]3 C1 L1 x0 _8 O上图表示:“si-j ~ si-1” == “t 0 ~ t j-1”,s i != t j(前面都相等,但比较到 t j 时发现不相等了)且next[j] == k。. o  @; m  }6 Y" I
    4444.png $ R4 L" }) S. B# E% u# y7 t7 V
    根据 next 数组的定义得知 “t k ~ t j-1” == “t 0 ~ t k-1”,所以 “t 0 ~ t k-1” == “si-k ~ si-1”
    $ ]$ w4 X4 c* T! I; m" N 555.png 1 H8 y/ U$ r3 @$ S* V
    将模式串右移,得到上图,这样就避免了目标穿的指针回溯。) k6 c* i3 _3 d+ a& B
    2 H. z. `9 Z: c: F

    . m+ Y3 O7 b3 m! s7 N/ [都明了之后就可以手写 KMP 的代码了1 p" Y. s8 w% N6 o, Z+ l: M

    2 ~4 W7 q( N' V7 C

    5 j7 B7 l5 {: K) zint KMP(String s,String t)
    # y; _6 u1 W" d; W+ J{; n* `0 A0 k* `/ S+ z0 L1 @' Y
       int next[MaxSize],i=0;j=0;
    3 ?5 h5 C  d6 r* \% P! R   Getnext(t,next);, G; X% [! ?2 S. P
       while(i<s.length&&j<t.length)8 e) {0 v" I' E; J2 q4 R( R' @
       {; O! h, l* a# c/ X8 x+ ?
          if(j==-1 || s==t[j])
    # w$ p; l2 K8 `  p      {: r% G4 ~, X& l! M  Q% |" ]2 y
             i++;
    : ~; T4 X! B5 z& H$ y         j++;
    2 I: d4 J  f8 K: D9 l. _      }7 m/ I8 e$ I1 y5 _$ w% O- Q3 l
          else j=next[j];               //j回退。。。
    ' a; N; X, F; j% B   }$ B2 J2 ~. E. N& ~* y% t, Q
       if(j>=t.length): ?1 m; K% ~9 z! T
           return (i-t.length);         //匹配成功,返回子串的位置
    5 N: |3 h) M/ B4 t0 k7 w) z   else
    2 [! l9 j0 t0 K" j$ |! Y      return (-1);                  //没找到
    5 k, ^8 V. e7 F9 E& @# m+ e}
    1 \% M6 c8 @3 A+ U; G: M, p! n' r0 u4 M: Q$ \' S6 @6 [4 y
    改进后的 next 求解方法' m/ r3 x+ w6 [- d  Y
    先来看一下上面算法存在的缺陷:
    7 P' R( [! X! O0 j 6666.png
    ; l$ G* x( C3 d显然,当我们上边的算法得到的next数组应该是[ -1,0,0,1 ]
    7 Y& m6 ]  t" M3 q  w# {& |8 X
    8 g  f$ R( o2 ?4 M/ s
    # v; S( s9 L, l2 E4 s
    所以下一步我们应该是把j移动到第1个元素咯:$ e; X9 A- j) a+ u
    7777.png
    , z5 E1 d( e& c3 p6 [  @3 u9 f" ]不难发现,这一步是完全没有意义的。因为后面的B已经不匹配了,那前面的B也一定是不匹配的,同样的情况其实还发生在第2个元素A上。" B! W3 n$ I( \! E
      U) ?. z6 k+ V9 j) Y& }' y" S
      Q- w7 z  `- a
    显然,发生问题的原因在于t[j] == t[next[j]]。
    " @( C$ D. E: D
    . h7 C/ Z) X% c5 C+ O
    ! K* g2 n, L/ G% |% ]& y' C. y% e# x
    所以我们需要谈价一个判断:9 L9 v  g9 P. _+ I1 N/ U8 Y2 `4 ^2 U

    $ A5 c, f0 N& [9 _) \; H! ^2 w3 @- S

    4 }  t' R: ]9 S1 [void Getnext(int next[],String t)
    9 q8 \* u, I# F/ j2 w1 D9 x; o0 @{4 |, v$ s  c9 X8 {: L
       int j=0,k=-1;
    2 R" L3 D! |) j  v   next[0]=-1;
    - ?+ u; O( D: b+ Q, `$ m/ w! e) j; P   while(j<t.length-1)
    1 r+ l% G5 x* p- ]& S: K% J, O7 I" K6 O   {4 C# `, T0 y( |
          if(k == -1 || t[j] == t[k])/ U7 Z$ R! A; h9 e' Q
          {5 o2 O, c4 z; T/ J
             j++;k++;
    9 g, Q0 q  m$ h- b3 h5 M         if(t[j]==t[k])//当两个字符相同时,就跳过
    . ~( j2 {' ^0 l$ U$ b8 g* d            next[j] = next[k];4 B/ Q! I: m4 X  c: l, ]* f
             else/ [7 }' d/ z4 c$ k# {1 X
                next[j] = k;! Y$ B+ J0 @# g5 }# i
          }# m* H0 h7 q% d4 I. I8 q$ T, a
          else k = next[k];
    / J' X* f+ p4 D* m( Z# G- y- x   }& x6 p5 v  y7 J. B5 ]; ]
    }
    2 l! W$ c  l! o" L————————————————) z& U: f/ |: f+ }% k  U4 a, e, u: \
    版权声明:本文为CSDN博主「June·D」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    5 q( N$ e/ E/ E; F* x/ J  \  V, W2 |原文链接:https://blog.csdn.net/dark_cy/article/details/88698736& i+ b+ T8 t% r% H$ F/ X

    0 n8 i, G% M) R- I5 @# X
    . @* g2 d& ?# m4 z6 M8 F4 o* }
    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-4-13 21:51 , Processed in 2.174597 second(s), 59 queries .

    回顶部