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. 将AD域漫游用户配置文件放在samba服务器中

    书接上回https://www.cnblogs.com/jackadam/p/11448497.html 我们已经将linux服务器设置为域成员,启动samba服务后,已经实现了使用域账号验证,自动创 ...

  2. MongoDB学习笔记二:使用Docker安装MongoDB

    目录 Docker安装MongoDB Docker给MongoDB设置用户密码 NoSQL Manager for MongoDB连接 为admin赋权限 上一个笔记介绍了Windows下安装Mong ...

  3. V8世界探险 (1) - v8 API概览

    版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/lusing/article/detai ...

  4. poj 2775 文件结构“图"

    总时间限制: 1000ms 内存限制: 65536kB 描述 在计算机上看到文件系统的结构通常很有用.Microsoft Windows上面的"explorer"程序就是这样的一个 ...

  5. 「中山纪中集训省选组D4T1」折射伤害 高斯消元

    题目描述 在一个游戏中有n个英雄,初始时每个英雄受到数值为ai的伤害,每个英雄都有一个技能"折射",即减少自己受到的伤害,并将这部分伤害分摊给其他人.对于每个折射关系,我们用数对\ ...

  6. Hibernate-validator数据验证

    前言 数据效验工作在开发工作中,是非常重要的,保证数据的正确性,可靠性,安全性.不仅在前端进行效验,还要在后台继续进行效验. 前端做验证只是为了用户体验,比如控制按钮的显示隐藏,单页应用的路由跳转等等 ...

  7. java笔记4—继承

    继承: 作用: 提高了代码的复用性. 让类与类之间产生了关系,为多态提供了前提 继承中成员变量的特点: 继承中成员函数的特点: 方法重写: 注意:重写方法必须和被重写的方法具有相同的方法名,参数列表和 ...

  8. 前端 html篇

    web开发本质: html是一个标准,规定了大家怎么写网页 1.浏览器输入网址回车发生了什么事 1. 浏览器 给服务端 发送了一个消息2. 服务端拿到消息3. 服务端返回消息4. 浏览器展示页面 se ...

  9. Python基础 — eval 函数的作用

    eval函数就是实现list.dict.tuple与str之间的相互转化str函数把list,dict,tuple转为为字符串 # 字符串转换成列表a = "[[1,2], [3,4], [ ...

  10. 【C#】上级实验四

    1.虚方法练习 设计一个控制台应用程序,定义一个Shape类,具体要求如下: ()类中定义2个私有字段长度(length).宽度(breadth). ()类中定义相应公有属性分别对应上述2个字段: ( ...