1. 引言

字符串匹配是极为常见的一种模式匹配。简单地说,就是判断主串TT中是否出现该模式串PP,即PP为TT的子串。特别地,定义主串为T[0…n−1]T[0…n−1],模式串为P[0…p−1]P[0…p−1],则主串与模式串的长度各为nn与pp。

暴力匹配

暴力匹配方法的思想非常朴素:

  1. 依次从主串的首字符开始,与模式串逐一进行匹配;
  2. 遇到失配时,则移到主串的第二个字符,将其与模式串首字符比较,逐一进行匹配;
  3. 重复上述步骤,直至能匹配上,或剩下主串的长度不足以进行匹配。

下图给出了暴力匹配的例子,主串T="ababcabcacbab",模式串P="abcac",第一次匹配:

第二次匹配:

第三次匹配:

C代码实现:

int brute_force_match(char *t, char *p) { int i, j, tem; int tlen = strlen(t), plen = strlen(p); for(i = 0, j = 0; i <= tlen - plen; i++, j = 0) { tem = i; while(t[tem] == p[j] & j < plen) { tem++; j++; } // matched if(j == plen) { return i; } } // [p] is not a substring of [t] return -1; }

时间复杂度i在主串移动次数(外层的for循环)有n−pn−p次,在失配时j移动次数最多有p−1p−1次(最坏情况下);因此,复杂度为O(n∗p)O(n∗p)。


我们仔细观察暴力匹配方法,发现:失配后下一次匹配,

  • 主串的起始位置 = 上一轮匹配的起始位置 + 1;
  • 模式串的起始位置 = 首字符P[0]

如此未能利用已经匹配上的字符的信息,造成了重复匹配。举个例子,比如:第一次匹配失败时,主串、模式串失配位置的字符分别为 a 与 c,下一次匹配时主串、模式串的起始位置分别为T[1]P[0];而在模式串中c之前是ab,未有重复字符结构,因此T[1]P[0]肯定不能匹配上,这样造成了重复匹配。直观上,下一次的匹配应从T[2]P[0]开始。

2. KMP算法

KMP思想

根据暴力方法的缺点,而引出KMP算法的思想。首先,一般化匹配失败,如下图所示:

在暴力匹配方法中,下一次匹配开始时,主串指针会回溯到i+1,模式串指针会回退到0。那么,如果不让主串指针发生回溯,模式串的指针应回退到哪个位置才能保证正确匹配呢?首先,我们从上图中可以得到已匹配上的字符:

T[i…i+j−1]=P[0…j−1]T[i…i+j−1]=P[0…j−1]

KMP算法思想便是利用已经匹配上的字符信息,使得模式串的指针回退的字符位置能将主串与模式串已经匹配上的字符结构重新对齐。当有重复字符结构时,下一次匹配如下图所示:

从图中可以看出,下一次匹配开始时,主串指针在失配位置i+j,模式串指针回退到m+1;模式串的重复字符结构:

T[i+j−m−1…i+j−1]=P[j−m−1…j−1]=P[0…m](1)(1)T[i+j−m−1…i+j−1]=P[j−m−1…j−1]=P[0…m]

且有

T[i+j]≠P[j]≠P[m+1]T[i+j]≠P[j]≠P[m+1]

那么应如何选取mm值呢?假定有满足式子(1)(1)的两个值m1>m2m1>m2,如下图所示:

如果选取m=m2m=m2,则会丢失m=m1m=m1的这一种字符匹配情况。由数学归纳法容易知道,应取所有满足式子(1)(1)中最大的mm值。


KMP算法中每一次的匹配,

  • 主串的起始位置 = 上一轮匹配的失配位置;
  • 模式串的起始位置 = 重复字符结构的下一位字符(无重复字符结构,则模式串的首字符)

模式串P="abcac"匹配主串T="ababcabcacbab"的KMP过程如下图:

部分匹配函数

根据上面的讨论,我们定义部分匹配函数(Partial Match,在数据结构书[2]称之为失配函数):

f(j)={max{m}−1P[0…m]=P[j−m…j],0≤m<jelsef(j)={max{m}P[0…m]=P[j−m…j],0≤m<j−1else

其表示字符串P[0…j]P[0…j]的前缀与后缀完全匹配的最大长度,也表示了模式串中重复字符结构信息。KMP中大名鼎鼎的next[j]函数表示对于模式串失配位置j+1,下一轮匹配时模式串的起始位置(即对齐于主串的失配位置);则

next[j]=f(j)+1next[j]=f(j)+1

如何计算部分匹配函数呢?首先来看一个例子,模式串P="ababababca"的部分匹配函数与next函数如下:

j 0 1 2 3 4 5 6 7 8 9  
P[j] a b a b a b a b c a  
f(j) -1 -1 0 1 2 3 4 5 -1 0  
next[j] 0 0 1 2 3 4 5 6 0 1  

模式串的f(j)满足P[0…f(j)]=P[j−f(j)…j]P[0…f(j)]=P[j−f(j)…j],在计算f(j+1)分为两类情况:

  • 若P[j+1]=P[f(j)+1]P[j+1]=P[f(j)+1],则有P[0…f(j)+1]=P[j−f(j)…j+1]P[0…f(j)+1]=P[j−f(j)…j+1],因此f(j+1)=f(j)+1
  • 若P[j+1]≠P[f(j)+1]P[j+1]≠P[f(j)+1],则要从P[0…f(j)]P[0…f(j)]中找出满足P[f(j+1)]=P[j+1]f(j+1),从而得到P[0…f(j+1)]=P[j+1−f(j+1)…j+1]P[0…f(j+1)]=P[j+1−f(j+1)…j+1]

其中,根据f(j)的定义有:

P[j]=P[f(j)]=P[f(f(j))]=⋯=P[fk(j)]P[j]=P[f(j)]=P[f(f(j))]=⋯=P[fk(j)]

其中,fk(j)=f(fk−1(j))fk(j)=f(fk−1(j))。通过上面的例子可知,函数fk(j)fk(j)是随着kk递减的,并最后收敛于-1。此外,P[j]p[j+1]相邻;因此若存在P[f(j+1)]=P[j+1],则必有

f(j+1)=fk(j)+1f(j+1)=fk(j)+1

为了求满足条件的最大的f(j+1),因fk(j)fk(j)是随着kk递减的,故应为满足上式的最小kk值。

综上,部分匹配函数的计算公式如下:

f(j)={fk(j−1)+1−1minkP[fk(j−1)+1]=P[j]elsef(j)={fk(j−1)+1minkP[fk(j−1)+1]=P[j]−1else

代码实现

部分匹配函数(失配函数)的C实现代码:

int *fail(char *p) { int len = strlen(p); int *f = (int *) malloc(len * sizeof(int)); f[0] = -1; int i, j; for(j = 1; j < len; j++) { for(i = f[j-1]; ; i = f[i]) { if(p[j] == p[i+1]) { f[j] = i + 1; break; } else if(i == -1) { f[j] = -1; break; } } } return f; }

KMP的C实现代码:

int kmp(char *t, char *p) { int *f = fail(p); int i, j; for(i = 0, j = 0; i < strlen(t) && j < strlen(p); ) { if(t[i] == p[j]) { i++; j++; } else if(j == 0) i++; else j = f[j-1] + 1; } return j == strlen(p) ? i - strlen(p) : -1; }

时间复杂度fail函数的复杂度为O(p)O(p),kmp函数的复杂度为O(n)O(n),所以整个KMP算法的复杂度为O(n+p)O(n+p)。

转载于:https://www.cnblogs.com/yulei126/p/6756233.html

KMP算法的来龙去脉相关推荐

  1. 【模式匹配】KMP算法的来龙去脉

    1. 引言 字符串匹配是极为常见的一种模式匹配.简单地说,就是判断主串T中是否出现该模式串P,即P为T的子串.特别地,定义主串为T[0-n−1],模式串为P[0-p−1],则主串与模式串的长度各为n与 ...

  2. KMP算法具体解释(转)

    作者:July. 出处:http://blog.csdn.net/v_JULY_v/. 引记 此前一天,一位MS的朋友邀我一起去与他讨论高速排序,红黑树,字典树,B树.后缀树,包含KMP算法,只有在解 ...

  3. KMP算法(多种实现方式)

    KMP算法核心思想 利用已经匹配的数据,去除无效的从头匹配 KMP算法流程 首先我们找到 i=9,j=9时不匹配,如果时暴力算法,此时i应重新来到i=2的位置,j返回j=1的位置,开始新一轮的匹配 这 ...

  4. BF算法优化-------KMP算法

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

  5. 数据结构与算法(5)字符串(BF算法、KMP算法及KMP算法优化)

    目录 一.BF算法(暴力算法) 二.KMP算法 三.KMP算法优化 一.BF算法(暴力算法) 一个一个往后匹配,匹配失败继续从母串下一个和头(子串的头)往后继续匹配. 虽然简单,但是需要较多的时间复杂 ...

  6. hiho 1015 KMP算法 CF 625 B. War of the Corporations

    #1015 : KMP算法 时间限制:1000ms 单点时限:1000ms 内存限制:256MB 描述 小Hi和小Ho是一对好朋友,出生在信息化社会的他们对编程产生了莫大的兴趣,他们约定好互相帮助,在 ...

  7. HDU 1711 Number Sequence(KMP算法)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1711 Number Sequence Time Limit: 10000/5000 MS (Java/ ...

  8. 算法(2)KMP算法

    1.0 问题描述 实现KMP算法查找字符串. 2.0 问题分析 "KMP算法"是对字符串查找"简单算法"的优化. 字符串查找"简单算法"是源 ...

  9. KMP算法求回溯数组的步骤

    KMP算法到底是什么原理就不说了,各种资料上讲的明明白白,下面我就如何用代码来实现做一下说明和记录. KMP的核心思想就是,主串不回溯,只模式串回溯.而模式串匹配到第几位时失配,要回溯多少,由模式串本 ...

最新文章

  1. python中api是指什么_python中API接口是什么
  2. ES6一些新特性记录
  3. “数据中台”是什么?
  4. 19-for循环语句
  5. 惊呆了!被公司辞退拿了22万补偿金,原东家称每月涨薪7000,只要退还22万
  6. 对ARP病毒攻击的防范和处理及相应工具方法
  7. 读书笔记:非营利组织的管理
  8. 怎样学好c语言程序设计这门,初学C语言程序设计的基本方法和技巧
  9. js页面间通信方法实现
  10. VSD Viewer for Mac(Visio绘图文件阅读器)
  11. Axure RP 8下载
  12. Maven整合SSM项目(七)
  13. cnpm : 无法加载文件 C:\Users\zsl\AppData\Roaming\npm\cnpm.ps1,因为在此系统上禁止运行脚本
  14. 网易或入股MSN中国 门户再现竞争新局
  15. vue 图表三维立体3D散点图
  16. android 取消蓝牙配对框 实现自动配对 输入PIN码
  17. java线程之生产者与消费者
  18. mc服务器控制台发消息,控制台作用介绍及控制台命令大全分享
  19. python if中的false(哪些视为False)
  20. Pythontip刷题记录

热门文章

  1. Gentoo 安装日记 02 (建立并启动虚拟机)
  2. 续: [转]Oracle 表空间与数据文件
  3. github 公钥 私钥_GitGithub入门教程笔记(2)
  4. 事务复制提示初始快照不可用_谈谈数据库的事务ACID
  5. 教育部双一流计算机名单,清华大学双一流学科名单34个【教育部公布】
  6. linux 平均磁盘请求数量,Linux之 iostat 解读磁盘io
  7. 共模电感适用的频率_电感选型详解及设计规范
  8. 超好用的几款可视化管理工具,你有吗?
  9. 如何在工作中快速成长?致工程师的 10 个简单技巧
  10. Serverless 解惑——函数计算如何安装字体