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. [LeetCode] 211. Add and Search Word - Data structure design 添加和查找单词-数据结构设计

    Design a data structure that supports the following two operations: void addWord(word) bool search(w ...

  2. 解决Python查询Mysql数据库信息乱码问题

    今天尝试着用 Python 写了个脚本试着连接 mysql 数据库,并查询里边的数据,不过最终查询结果中文字符变成了ascii格式. 代码如下: #!/usr/bin/python #encoding ...

  3. LeetCode 1047. 删除字符串中的所有相邻重复项(Remove All Adjacent Duplicates In String)

    1047. 删除字符串中的所有相邻重复项 1047. Remove All Adjacent Duplicates In String 题目描述 LeetCode1047. Remove All Ad ...

  4. python函数知识六 内置函数二、匿名函数与内置函数三(重要)

    19.内置函数二 abs():绝对值 lst = [1,2,-3,1,2,-5] print([abs(i) for i in lst]) enumerate("可迭代对象",&q ...

  5. 对JAVA工程师绝对有用的Java学习资源清单

    学习Java和其他技术的资源其实非常多,但也不是都是好的有用的,我们要取其精华去其糟粕,选择那些最好的,最适合我们的,同时也要由浅入深,先易后难.基于这样的一个标准,我在这里为大家提供一份Java的学 ...

  6. docker深入学习二

    dicker:数据管理 数据管理机制 docker使用union file system来管理数据,docker构建image和container也是采用了同样的技术. image层次 iamge由多 ...

  7. python threading模块的Lock和RLock区别

    首先了解这两者是什么. 以下说明参考自python官网 Lock:Lock被称为①原始锁,原始锁是一个②在锁定时不属于特定线程的同步基元组件,它是能用的最低级的同步基元组件.原始锁处于 "锁 ...

  8. Java线程volatile(二)

    volatile:使变量在多个线程中可见 在java 中每个线程都会有一块工作内存区,其中存放着所有线程共享的主内存中变量的拷贝.当线程执行时,在自己的工作内存区操作这些变量,为了存取一个共享的变量, ...

  9. 跨域访问MVC

    using MvcApp.Filters; using System; using System.Collections.Generic; using System.Linq; using Syste ...

  10. C# 连接SQLServer数据库自动生成model类代码

    Program.cs using System; using System.Collections.Generic; using System.Linq; using System.Threading ...