字符串匹配算法是非常常见的算法。考虑长度为nnn的文本(text)字符串A[1,2,⋯,n]A[1,2,\cdots,n]A[1,2,⋯,n],长度为mmm的匹配(pattern)字符串B[1,2,⋯,m]B[1,2,\cdots,m]B[1,2,⋯,m],并且m≤nm\leq nm≤n。暴力求解(brute-force)的匹配算法十分直接。将BBB逐位与AAA进行对比,直到BBB完全匹配AAA的某个子串。例如,先拿BBB与A[1,2,⋯,m]A[1,2,\cdots,m]A[1,2,⋯,m]匹配,如果失败,尝试匹配BBB与A[2,3,⋯,m+1]A[2,3,\cdots,m+1]A[2,3,⋯,m+1],以此类推,直到匹配BBB与A[n−m+1,⋯,n]A[n-m+1,\cdots,n]A[n−m+1,⋯,n]。该方法的时间复杂度为O(mn)O(mn)O(mn),详细的分析可以参考参考文献[2]。
   Knuth, Morris和Pratt三人提出了时间复杂度为线性的KMP算法。该算法将时间复杂度从暴力求解的O(mn)O(mn)O(mn)降低为O(n+m)O(n+m)O(n+m)。下面详细讨论该算法,主要参考参考文献[1]。
   考虑下面的图示,其中文本字符串记为TTT,匹配字符串记为PPP。匹配字符串为P=′ababaca′P='ababaca'P=′ababaca′。当匹配进行到(a)所示的这一步时,PPP相对于TTT移动了sss位,并且前5位均能正确匹配,匹配失败在第6位。此时,P[6]=′c′P[6]='c'P[6]=′c′,对应的T[s+6]=′a′T[s+6]='a'T[s+6]=′a′。假如我们正在使用暴力求解算法,当前的匹配失败后,此时我们需要将PPP向右再移动移位,即总体相对于TTT移动s+1s+1s+1位,使得P[1]=′a′P[1]='a'P[1]=′a′对准T[s+2]=′b′T[s+2]='b'T[s+2]=′b′,开始新的匹配。显然,这样的匹配也是失败的,并且在第一位就失败了。于是,继续移动PPP,将它右移一位,使得P[1]=′a′P[1]='a'P[1]=′a′对准T[s+3]=′a′T[s+3]='a'T[s+3]=′a′,再次开始匹配。
   在观察上面的匹配过程的时候,我们发现,其实我们在PPP移动sss位的这次匹配失败后,可以直接右移两位,而不是一位。右移两位是因为我们可以看到T[s+2]=′a′=P[1]T[s+2]='a'=P[1]T[s+2]=′a′=P[1],而移动移位之后对准的T[s+1]T[s+1]T[s+1]显然和P[1]P[1]P[1]不相等。这种移位,减少了不必要的匹配。

   在右移两位之后,开始新的匹配,如(b)所示,此时需要考虑一个问题,那就是我们还需要从第一位P[1]P[1]P[1]开始匹配吗?显然不是,从图中可以看出,P[1,2,3]P[1,2,3]P[1,2,3]已经和T[s+3,s+4,s+5]T[s+3,s+4,s+5]T[s+3,s+4,s+5]匹配好了,只需要从P[4]P[4]P[4]开始匹配。如此一来,相较于暴力求解,又减少了匹配次数。可问题是,我们怎么知道前几是匹配好了,然后从某个点开始新匹配呢?例如在(b)中,我们如何知道前3个点是匹配的,从而从第4个点开始匹配?显然,在(a)的匹配中,我们已经比较过[s+3,s+4,s+5][s+3,s+4,s+5][s+3,s+4,s+5]的值了,因此我们可以通过某种手段,将他们的信息储存起来,这种储存方式不一定是显性的,他可以是某种隐含地方式。
   为实现上面分析的想法,我们引入一个辅助(auxiliary)序列π[1,2,⋯,m]\pi[1,2,\cdots,m]π[1,2,⋯,m],他和PPP等长。辅助序列是实现上述算法思想的关键。从(a)到(b)的关键是需要知道P[1]P[1]P[1]和T[s+1]T[s+1]T[s+1]往后的元素中的哪一个是匹配的,我们就把PPP移动到P[1]P[1]P[1]与之对齐。在(a)中,匹配失败于P[6]P[6]P[6],假如辅助序列的相邻位可以提供给我们信息,告诉我们现在可以右移2位,使得P[1]P[1]P[1]与T[s+3]T[s+3]T[s+3]是匹配的,那我们的想法就实现了。比如π[5]\pi[5]π[5]这个元素告诉我们可以右移2位,即π[5]=2\pi[5]=2π[5]=2。
   实际上,π\piπ中的元素π[i]\pi[i]π[i]表示的是在序列B[1,2,⋯,i]B[1,2,\cdots,i]B[1,2,⋯,i]中,最多有前π[i]\pi[i]π[i]个元素和后π[i]\pi[i]π[i]个元素对应相等,即B[1,2,⋯,π[i]]=B[i−π[i]+1,i−π[i]+3⋯,i]B[1,2,\cdots,\pi[i]]=B[i-\pi[i]+1,i-\pi[i]+3\cdots,i]B[1,2,⋯,π[i]]=B[i−π[i]+1,i−π[i]+3⋯,i]。例如,上图中的PPP对应的π\piπ为π=[0,0,1,2,3,0,1]\pi=[0,0,1,2,3,0,1]π=[0,0,1,2,3,0,1]。有了π\piπ,我们再来看如何由(a)变到(b)。在(a)中,匹配于P[6]P[6]P[6]失败,于是我们查询其前一位的辅助序列元素π[5]=3\pi[5]=3π[5]=3。π[5]=3\pi[5]=3π[5]=3意味着P[1,2,3]=P[3,4,5]P[1,2,3]=P[3,4,5]P[1,2,3]=P[3,4,5]。此外,我们的匹配在P[6]P[6]P[6]失败,意味着之前的匹配是成功的,于是有T[s+1,⋯,s+5]=P[1,⋯,5]T[s+1,\cdots,s+5]=P[1,\cdots,5]T[s+1,⋯,s+5]=P[1,⋯,5],结合P[1,2,3]=P[3,4,5]P[1,2,3]=P[3,4,5]P[1,2,3]=P[3,4,5],于是有P[1,2,3]=P[3,4,5]=T[s+3,s+4,s+5]P[1,2,3]=P[3,4,5]=T[s+3,s+4,s+5]P[1,2,3]=P[3,4,5]=T[s+3,s+4,s+5],于是我们需要将PPP右移π[6−1]−1=2\pi[6-1]-1=2π[6−1]−1=2位,使得P[1,2,3]P[1,2,3]P[1,2,3]与T[s+3,s+4,s+5]T[s+3,s+4,s+5]T[s+3,s+4,s+5]对齐。新的匹配从P[4]P[4]P[4]与T[s+6]T[s+6]T[s+6]开始。需要注意的是,从(a)到(b),虽然PPP移位了,并且新的匹配点变成了P[4]P[4]P[4],但是TTT的匹配点并没有变,仍然是T[s+6]T[s+6]T[s+6]。
   辅助序列π\piπ的生成算法如下。他的思想是,对于某个π[q]\pi[q]π[q],k=π[q−1]k=\pi[q-1]k=π[q−1],这意味着P[1,2,⋯,k]=P[q−k+1,q−k+2,⋯,q]P[1,2,\cdots,k]=P[q-k+1,q-k+2,\cdots,q]P[1,2,⋯,k]=P[q−k+1,q−k+2,⋯,q]。比较当前P[k+1]P[k+1]P[k+1]是否与P[q]P[q]P[q]匹配,如果匹配,则π[q]=k+1\pi[q]=k+1π[q]=k+1。如果不匹配,则寻找前面某个kkk,使得P[k+1]=P[q]P[k+1]=P[q]P[k+1]=P[q]。寻找前面的某个kkk,我们还得使匹配序列的长度尽量大,因此,令k=π[k]k=\pi[k]k=π[k]。

   KMP的主算法如下,它调用了上面的辅助序列生成算法,并且与辅助序列生成算法在形式上十分相似。

   下面分析KMP算法的时间复杂度。很多网上的博客都没有讲清楚其复杂度的分析,大多数点出用摊还(amortized)分析法,这里我们直接引用参考文献[2]的分析方法,简单易懂。由于KMP主算法的结构与序列生成算法几乎一样,所以我们分析序列生成算法的时间复杂度,KMP主算法的分析类似可得。
   序列生成算法的时间复杂度主要由第5行的for循环里面的内容决定。第10行的赋值,其时间复杂度是O(m)O(m)O(m),这是显然的。剩下的需要分析的是第7行和第9行的执行次数。这两行均是对kkk的值进行改变,因此我们研究一下kkk的取值区间。在刚进入for循环的时候,kkk被赋值为0,而qqq被赋值为2。在for循环中,只有第9行执行的时候,kkk才增加1。而每一次for循环,qqq都会增加1。每次循环不一定执行第9行。于是,我们知道,整个算法中,都有k<q≤mk<q\leq mk<q≤m。进一步,第10行赋值π[q]=k\pi[q]=kπ[q]=k,因此,π[q]<q\pi[q]<qπ[q]<q。换个符号,也等价于π[k]<k\pi[k]<kπ[k]<k。所以,第7行的赋值意味着减小kkk。至此,我们知道,第7行减小kkk,第9行增加kkk,并且k<q≤mk<q\leq mk<q≤m。因为第9行执行的次数最多为mmm,所以第7行执行的次数也不会超过mmm次。综上,序列生成算法中所有步骤的时间复杂度均是O(m)O(m)O(m),所以算法的总时间复杂度就是O(m)O(m)O(m)。同理,KMP主算法的时间复杂度是O(n)O(n)O(n),整个KMP算法的时间复杂度是O(n+m)O(n+m)O(n+m)。

参考文献

[1] Cormen T H, Leiserson C E, Rivest R L, et al,Introduction to algorithms,2009。
[2] 阮行止​,如何更好地理解和掌握 KMP 算法?,2020-02-23。

字符串匹配KMP算法及其时间复杂度分析相关推荐

  1. kmp算法详解php,php中字符串匹配KMP算法实现例子

    KMP算法是一个比较高级的算法了,加了改进了,下面我们来在php中实现KMP算法,希望例子对各位同学会带来帮助哦. kmp算法是一种改进的字符串匹配算法,由D.E.Knuth与V.R.Pratt和J. ...

  2. C++实现字符串匹配KMP算法

    文章目录 1. 概述 2. 代码实现 3. 代码测试 1. 概述 Kmp算法的介绍及思想参阅下面两篇文章: 字符串匹配KMP算法 算法)通俗易懂的字符串匹配KMP算法及求next值算法 2. 代码实现 ...

  3. 字符串匹配 KMP算法

    问题描述:字符串匹配即查找待匹配字符串(模式串)p在主串s中的位置.一般处理这种问题往往采用简单粗暴的方法--暴力匹配法.所谓暴力匹配法,就是对主串s的每一个字符与要匹配的字符串p的每个字符进行逐一匹 ...

  4. 字符串匹配——KMP算法

    字符串匹配--KMP算法 ​ 字符串匹配是计算机编程中最常使用到的基础算法之一.字符串匹配相关的算法很多,Knuth-Morris-Pratt(KMP)算法是最常用的之一.最近在学习KMP算法,学习了 ...

  5. 【数据结构与算法】字符串匹配 KMP 算法

    单模式串匹配 BF 算法和 RK 算法 BM 算法和 KMP 算法 多模式串匹配算法 Trie 树和 AC 自动机 KMP 算法 KMP 算法是根据三位作者(D.E.Knuth,J.H.Morris ...

  6. 字符串匹配——KMP算法【C语言】

    KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt提出的,因此人们称它为克努特-莫里斯-普拉特操作(简称KMP算法).KMP算法的核心是利用匹配失败后 ...

  7. 字符串匹配KMP算法

    字符串匹配KMP KMP过程其实就是去找下一个更好的状态的过程,省略去了中间穷举的无用过程,直接跳到下一个更好的状态,通过模式串本身的信息,站在模式串的角度来考虑问题 取长的一对 若想让模式串直接从S ...

  8. 字符串匹配KMP算法的讲解C++

    转自http://blog.csdn.net/starstar1992/article/details/54913261 也可以参考http://blog.csdn.net/liu940204/art ...

  9. 字符串匹配KMP算法的理解(详细)

    1. 引言 本KMP原文最初写于2年多前的2011年12月,因当时初次接触KMP,思路混乱导致写也写得混乱.所以一直想找机会重新写下KMP,但苦于一直以来对KMP的理解始终不够,故才迟迟没有修改本文. ...

最新文章

  1. php session举例,PHP 中session的经典用法
  2. Android:BroadcastReceiver的基础知识
  3. 2021云数据库RDS重磅升级发布会
  4. 【STM32】FreeRTOS列表应用示例
  5. Scala入门到精通—— 第二节Scala基本类型及操作、程序控制结构
  6. scss支持的嵌套css规则
  7. 批准Oracle IDM中的特定Web服务
  8. 被吐嘈的NodeJS的异常处理
  9. 学习笔记 :E1696 C1107 错误提示
  10. innobackupex参数之 --throttle 限速这个值设置多少合理 原创
  11. Pandas入门教程(三)
  12. jquery proxy delegate 的比较
  13. 【故障处理】ORA-19809错误处理
  14. Android手机便携式wifi的使用及无线数据传输(主要针对XP系统)
  15. DL之RNN:人工智能为你写诗——基于TF利用RNN算法实现【机器为你写诗】、训练测试过程全记录
  16. Chrome浏览器主页被篡改怎么修复
  17. 3.8 js过渡效果
  18. github项目ratel:JAVA实现斗地主
  19. 骨传导原理是什么?骨传导耳机对保护耳朵健康有帮助吗?
  20. (Python)视频生成器

热门文章

  1. Object.keys、Object.values、Object.entries详解
  2. Hdu 1878 欧拉回路[判断是否存在欧拉回路]
  3. 看 Sugar 如何说 I2C 通信
  4. 外汇天眼:Axi收回在RGT Capital的全部控制权,Eurotrader获得FCA牌照
  5. 开源的B2B网站 及 B2B、B2C、C2C的讲解
  6. 大疆从无人机中来,极飞到无人机中去
  7. 【Unity】 HTFramework框架(十九)ILHotfix热更新模块
  8. Intellij IDEA 安装和配置热部署插件JRebel进行项目的热部署
  9. 西北乱跑娃 --- windows下kill进程
  10. 《 中国高校鄙视链大全 》