KMP算法曾被我戏称为看毛片算法,当时笑喷......大三那个时候硬着头皮把算法导论的kmp算法啃完,弄懂了kmp算法

的原理,甚至还写出了代码,这几天再次温习的时候,发现忘得比较彻底。我总结,学算法不能只对着书本学理论,而应该

用自己的理解去看清算法的本质,最好用文字把你的理解记录下来,这样才能做到活学活用,而且不容易忘。写这篇博客就是想把自己这几天的思路记下来。

一 kmp算法为什么比传统的字符串匹配算法快

假设文本T = y1y2y3....yn, 模式 P = p1p2p3...pm, 传统的匹配算法把位移为0,1,...n-m时的文本依次跟P比较,每次比较最多花费O(m)的时间,算法的复杂度为O((n-m+1)*m)。这种算法没有利用匹配过的信息,每次都从头开始比较,速度很慢。而kmp算法充分利用了之前的匹配信息,从而避免一些明显不合法的位移。加快匹配过程。来看一个例子:

#########000xxxx000######                       文本T

|<---- s ---->|000xxxx000~~~                              模式P

假设位移为s时,T和P匹配了红色部分的字符,即匹配到了模式P的前10个字符,如果按照传统的匹配方法,下一步就是从位移s+1开始比较,而kmp算法则直接从位移s+7开始比较,而且断定:位移s+7对应的串和模式P的前3个字符是相同的,可

以不用比较,直接从第4个字符开始比较,这种跳跃式的匹配是不是比传统匹配方法快很多,如下图所示:

#########000xxxx000######                       文本T

|<-------- s+7-------->| 000xxxx000~~~               模式P

那么kmp是如何实现这种跳跃的呢?注意到红色部分的字符,即模式P的前10个字符,有一个特点:它的开始3个字符和末尾

3个字符是一样的,又已知文本T也存在红色部分的字符,我们把位移移动 10-3 = 7个位置,让模式P的开始3个字符对准文本

T红色部分的末尾3个字符,那么它们的前3个字符必然可以匹配。

二 构造前缀数组

上面的例子是文本T和模式P匹配了前面10个字符的情况下发生的,而且我们观察到模式P的前缀P10中,它的开始3个字符和末尾3个字符是一样的。如果对于模式P的所有前缀P1,P2...Pm,都能求出它们首尾有多少个字符是一样的,当然相同的字

符数越多越好,那么就可以按照上面的方法,进行跳跃式的匹配。

定义:

Pi表示模式P的前i个字符组成的前缀, next[i] = j表示Pi中的开始j个字符和末尾j个字符是一样的,而且对于前缀Pi来说,这样

的j是最大值。next[i] = j的另外一个定义是:有一个含有j个字符的串,它既是Pi的真前缀,又是Pi的真后缀

规定:

next[1] = next[0] = 0

next[i]就是前缀数组,下面通过1个例子来看如何构造前缀数组。

例子1:cacca有5个前缀,求出其对应的next数组。

前缀2为ca,显然首尾没有相同的字符,next[2] = 0

前缀3为cac,显然首尾有共同的字符c,故next[3] = 1

前缀4为cacc,首尾有共同的字符c,故next[4] = 1

前缀5为cacca,首尾有共同的字符ca,故next[5] = 2

如果仔细观察,可以发现构造next[i]的时候,可以利用next[i-1]的结果。假设模式已求得next[10] = 3,如下图所示:

000#xxx000         前缀P10

000                        末尾3个字符

根据前缀函数的定义:next[10] = 3意味着末尾3个字符和P10的前3个字符是一样的

为求next[11],可以直接比较第4个字符和第11个字符,如下图所示:蓝色和绿色的#号所示,如果它们相等,则

next[11] = next[10]+1 = 4,这是因为next[10] = 3保证了前缀P11和末尾4个字符的前3个字符是一样的.

000#xxx000#       前缀P11

000#                      末尾4个字符

所以只需验证第4个字符和第11个字符。但如果这两个字符不想等呢?那就继续迭代,利用next[next[10] = next[3]的值来求

next[11]。代码如下:

  1. void compute_prefix(int *next, char *p)
  2. {
  3. int     i, n, k;
  4. n = strlen(p);
  5. next[1] = next[0] = 0;
  6. k = 0;      /* 第i次迭代开始之前,k表示next[i-1]的值 */
  7. for (i = 2; i <= n; i++) {
  8. for (; k != 0 && p[k] != p[i-1]; k = next[k]);
  9. if (p[k] == p[i-1]) k++;
  10. next[i] = k;
  11. }
  12. }

三 模拟KMP的查找过程

这里实现的算法与算法导论中的不一样,我觉得这种方法更加直观,思路就是模拟第1部分介绍的方法,每次匹配的时候

都利用上一次匹配信息,对于模式P,从第next[q]个字符开始比较,对于文本T,用一个变量s指示将要从哪个位置开始比较

,迭代开始之前,就从s这个位置开始。

  1. void kmp_match(char *text, char *p, int *next)
  2. {
  3. int     m, n, s, q;
  4. m = strlen(p);
  5. n = strlen(text);
  6. q = s = 0;  /* q表示上一次迭代匹配了多少个字符,
  7. s表示这次迭代从text的哪个字符开始比较 */
  8. while (s < n) {
  9. for (q = next[q]; q < m && p[q] == text[s]; q++, s++);
  10. if (q == 0) s++;
  11. else if (q == m) {
  12. printf("pattern occurs with shift %d\n", s-m);
  13. }
  14. }
  15. }

四 测试

  1. int main()
  2. {
  3. int     next[101], n;
  4. char    *p = "ca";
  5. char    *text = "cacca";
  6. compute_prefix(next, p);
  7. kmp_match(text, p, next);
  8. return 0;
  9. }

一篇别人写的Kmp算法的讲解,多看多得的更多相关文章

  1. 用erlang写的kmp算法

    Knuth-Morris-Pratt 字符串查找算法,简称为 "KMP算法",常用于在一个文本串S内查找一个模式串P 的出现位置,这个算法由Donald Knuth.Vaughan ...

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

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

  3. KMP算法入门讲解

    字符串匹配问题.假设文本是一个长度为$n$的字符串$T$,模板是一个长度为$m$的字符串$P$,且$m\leq n$.需要求出模板在文本中的所有匹配点$i$,即满足$T[i]=P[0],T[I+1]= ...

  4. [Algorithm] 字符串匹配算法——KMP算法

    1 字符串匹配 字符串匹配是计算机的基本任务之一. 字符串匹配是什么?举例来说,有一个字符串"BBC ABCDAB ABCDABCDABDE",我想知道,里面是否包含另一个字符串& ...

  5. 字符串匹配的KMP算法

    ~~~摘录 来源:阮一峰~~~ 字符串匹配是计算机的基本任务之一. 举例来说,有一个字符串”BBC ABCDAB ABCDABCDABDE”,我想知道,里面是否包含另一个字符串”ABCDABD”? 许 ...

  6. 深入理解KMP算法

    前言:本人最近在看<大话数据结构>字符串模式匹配算法的内容,但是看得很迷糊,这本书中这块的内容感觉基本是严蔚敏<数据结构>的一个翻版,此书中给出的代码实现确实非常精炼,但是个人 ...

  7. KMP算法详解 --- 彻头彻尾理解KMP算法

    前言 之前对kmp算法虽然了解它的原理,即求出P0···Pi的最大相同前后缀长度k. 但是问题在于如何求出这个最大前后缀长度呢? 我觉得网上很多帖子都说的不是很清楚,总感觉没有把那层纸戳破, 后来翻看 ...

  8. 字符串匹配的KMP算法详解及C#实现

    字符串匹配是计算机的基本任务之一. 举例来说,有一个字符串"BBC ABCDAB ABCDABCDABDE",我想知道,里面是否包含另一个字符串"ABCDABD" ...

  9. 字符串匹配与KMP算法实现

    >>字符串匹配问题 字符串匹配问题即在匹配串中寻找模式串是否出现, 首先想到的是使用暴力破解,也就是Brute Force(BF或蛮力搜索) 算法,将匹配串和模式串左对齐,然后从左向右一个 ...

随机推荐

  1. C# 与 .NET Framework 对应关系

    C#各版本新增加功能(系列文章)   本系列文章主要整理并介绍 C# 各版本的新增功能. C# 8.0 C#8.0 于 2019年4月 随 .NET Framework 4.8 与 Visual St ...

  2. [转帖]首颗国产DRAM芯片的技术与专利,合肥长鑫存储的全面深度剖析

    首颗国产DRAM芯片的技术与专利,合肥长鑫存储的全面深度剖析 https://mp.weixin.qq.com/s/g_gnr804q8ix4b9d81CZ1Q 2019.11 存储芯片已经成为全球珍 ...

  3. Java开发笔记(一百二十七)Swing的标签

    提起AWT的标签控件Label,那个使用体验可真叫糟糕,不但不支持文字换行,而且对中文很不友好,既可能把中文显示为乱码,还不支持博大精深的各种中文字体.所幸Swing的升级版标签JLabel在各方面都 ...

  4. C++中的强制类型转换

    在C语言中,强制类型转换的方式为(Type)Expression,另外还有一种现在已经不用的旧式写法Type(Expression),这两种方式是等价的. 但是,C语言的强制类型转换方式存在一些问题: ...

  5. SQL——BETWEEN操作符

    一.BETWEEN操作符的基本用法 BETWEEN操作符用于选取两个值范围内的值. 语法格式如下: SELECT 列名1,列名2... FROM 表名 WHERE 列名 BETWEEN 值1 AND ...

  6. PAT(B) 1060 爱丁顿数(Java:21分)

    题目链接:1060 爱丁顿数 (25 point(s)) 题目描述 英国天文学家爱丁顿很喜欢骑车.据说他为了炫耀自己的骑车功力,还定义了一个"爱丁顿数" E ,即满足有 E 天骑车 ...

  7. Fully Convolutional Networks for Semantic Segmentation 译文

    Fully Convolutional Networks for Semantic Segmentation 译文 Abstract   Convolutional networks are powe ...

  8. ArcGIS JS 之通过代理配置ArcGIS Server服务 token

    ArcGIS JS 代理 1.ArcGIS Server Manager中配置访问服务的用户名.密码.角色等,并配置地图服务的具体角色名称 2.在ArcGIS JS中配置使用代理,下述代码将esri的 ...

  9. LOJ2074/2157 JSOI2016/POI2011 Lightning Conductor 决策单调性DP

    传送门 我们相当于要求出\(f_i = \max\limits_{j=1}^{n} (a_j + \sqrt{|i-j|})\).这个绝对值太烦人了,考虑对于\(i>j\)和\(i<j\) ...

  10. Spark 系列(六)—— 累加器与广播变量

    一.简介 在 Spark 中,提供了两种类型的共享变量:累加器 (accumulator) 与广播变量 (broadcast variable): 累加器:用来对信息进行聚合,主要用于累计计数等场景: ...