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. Visual Studio特性学习

    官方档案: https://docs.microsoft.com/en-us/visualstudio/get-started/visual-studio-ide?view=vs-2019

  2. 喜马拉雅 FM 已购付费音频下载

    如何下载在喜马拉雅 FM 中已购买的付费音频.之前想分享自己购买的付费音频给朋友听,碍于喜马拉雅 FM 的音频不能直接导出,所以准备自己搞个下载的小软件. 仅可下载已购买的付费音频.当然,如果你是会员 ...

  3. [转帖]Linux企业运维人员最常用150个命令汇总

    Linux企业运维人员最常用150个命令汇总 https://clsn.io/clsn/lx998.html 基本上都用过了. 命令 功能说明 线上查询及帮助命令(2个) man 查看命令帮助,命令的 ...

  4. Spring Boot之从Spring Framework装配掌握SpringBoot自动装配

    Spring Framework模式注解 模式注解是一种用于声明在应用中扮演“组件”角色的注解.如 Spring Framework 中的 @Repository 标注在任何类上 ,用于扮演仓储角色的 ...

  5. Spring笔记3

    动态代理** 特点:字节码随用随创建,随用随加载 作用:不修改源码的基础上对方法增强 分类: ​ 基于接口的动态代理 ​ 基于子类的动态代理 基于接口的动态代理: 涉及的类:Proxy 如何创建代理对 ...

  6. C++Primer 5th Chap2 Variables and basic Types

    wchar_t,char16_t,char32_t用于拓展字符集 char和signed char并不一样,由编译器决定类型char表现上述两种中的哪一种 一般long的大小和int无二,如果超过in ...

  7. Python 运算符 各类运算符总结

    运算符详解2.1.算术运算符2.2.比较(关系)运算符2.3.赋值运算符2.4.逻辑运算符2.5.位运算符2.6.成员运算符2.7.身份运算符三.重要运算符说明3.1.join和符号”+“区别3.2. ...

  8. 【LEETCODE】54、数组分类,简单级别,题目:605、532

    数组类,简单级别完结.... 不容易啊,基本都是靠百度答案.... 希望做过之后后面可以自己复习,自己学会这个解法 package y2019.Algorithm.array; /** * @Proj ...

  9. Ali-Tomcat 安装

    通过在 Eclipse 安装 Tomcat4e 插件,或者在 Intellij Idea 安装配置 Ali-tomcat,可以快 速方便地启动并调试基于 EDAS 服务化框架 HSF 开发的应用. 1 ...

  10. SpringCloud之Zuul网关简介、映射vs过滤配置使用

    目的: Zuul路由网关简介及基本使用 Zuul路由映射配置 Zuul请求过滤配置 Zuul路由网关简介及基本使用 Zuul简介: Zuul是从设备和网站到Netflix流应用程序后端的所有请求的前门 ...