字符串匹配的KMP算法和C语言代码,不需要思考就能理解</h1>

2024-05-28 16:38

本文主要是介绍字符串匹配的KMP算法和C语言代码,不需要思考就能理解</h1>,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

KMP算法用于判断一个字符串是否包含另一个字符串,如果包含就返回脚标。其实KMP算法本身特别简单,我看了几篇本章都号称简单易懂,结果看得我云里雾里,直到我看到了阮一峰:字符串匹配的KMP算法,才真正看懂。下文的第一部分基本上都是阮一峰文章的转述,第二部分代码也是在网上其他地方找的。第三部分KMP算法的理解,才是笔者的东西,相信看了第三部分你会豁然开朗。

一、KMS算法的处理过程

下面用 KMP算法在字符串"BBC ABCDAB ABCDABCDABDE"中寻找字符串"ABCDABD":

首先,字符串"BBC ABCDAB ABCDABCDABDE"的第一个字符与搜索词"ABCDABD"的第一个字符,进行比较。因为B与A不匹配,所以搜索词后移一位。

因为B与A不匹配,搜索词再往后移。

就这样,直到字符串有一个字符,与搜索词的第一个字符相同为止。

接着比较字符串和搜索词的下一个字符,还是相同。

直到字符串有一个字符,与搜索词对应的字符不相同为止。

这时,最自然的反应是,将搜索词整个后移一位,再从头逐个比较。这样做虽然可行,但是效率很差,因为你要把"搜索位置"移到已经比较过的位置,重比一遍。

一个基本事实是,当空格与D不匹配时,你其实知道前面六个字符是"ABCDAB"。KMP 算法的想法是,设法利用这个已知信息,不要把"搜索位置"移回已经比较过的位置,继续把它向后移,这样就提高了效率。

怎么做到这一点呢?可以针对搜索词,算出一张《部分匹配表》(Partial Match Table)。这张表是如何产生的,后面再介绍,这里只要会用就可以了。

    已知空格与D不匹配时,前面六个字符"ABCDAB"是匹配的。查表可知,最后一个匹配字符B对应的"部分匹配值"为2,因此按照下面的公式算出向后移动的位数:

    移动位数 = 已匹配的字符数 - 对应的部分匹配值

    因为 6 - 2 等于4,所以将搜索词向后移动 4 位。

移动完后,再比较,发现空格与C还是不匹配,于是搜索词还要继续往后移。这时,已匹配的字符数为2("AB"),对应的"部分匹配值"为0。所以,移动位数 = 2 - 0,结果为 2,于是将搜索词向后移 2 位。

这时空格与A还是不匹配,继续后移一位。(因为搜索词已经移动到头了,所以默认的移动距离为1位)

这个时候发现能匹配了,于是又逐位比较,直到发现C与D不匹配。于是,移动位数 = 6 - 2,继续将搜索词向后移动 4 位。

这下就完全匹配了。

下面介绍《部分匹配表》是如何产生的。

  首先,要了解两个概念:"前缀"和"后缀"。 "前缀"指除了最后一个字符以外,一个字符串的全部头部组合;"后缀"指除了第一个字符以外,一个字符串的全部尾部组合。

        比如“bread”这个字符串的前缀是除了最后一个字符“d”,还剩下"brea",它的全部组合字符串有:"b"、"br"、"bre"、"brea"。

        “bread”这个字符串的后缀是除了第一个字符“b”,还剩下"read",它的全部组合字符串有:"r"、"re"、"rea"、"read"。

        "部分匹配值"就是"前缀"和"后缀"的最长的共有元素的长度。以"ABCDABD"为例,

-"A"的前缀和后缀都为空集,共有元素的长度为0;

-"AB"的前缀为[A],后缀为[B],共有元素的长度为0;

-"ABC"的前缀为[A, AB],后缀为[BC, C],共有元素的长度0;

-"ABCD"的前缀为[A, AB, ABC],后缀为[BCD, CD, D],共有元素的长度为0;

-"ABCDA"的前缀为[A, AB, ABC, ABCD],后缀为[BCDA, CDA, DA, A],共有元素为"A",长度为1;

-"ABCDAB"的前缀为[A, AB, ABC, ABCD, ABCDA],后缀为[BCDAB, CDAB, DAB, AB, B],共有元素为"AB",长度为2;

-"ABCDABD"的前缀为[A, AB, ABC, ABCD, ABCDA, ABCDAB],后缀为[BCDABD, CDABD, DABD, ABD, BD, D],共有元素的长度为0。

         "部分匹配值"其实还可以定义为:把字符串沿着中间的字符折叠,字符串首尾重叠的字符个数。

        比如“ABC”沿着字符“B”折叠,首尾的“A”和“C”就不能重叠,所以"部分匹配值"为0.

        "ABCDA"沿着字符“C”折叠,首尾的“A”能重叠,所以"部分匹配值"为1.

        "ABCDAB"沿着字符“C”和“D”的中间折叠,首尾的“AB”能重叠,所以"部分匹配值"为2.

二、C语言程序

        利用上面KMS的处理过程和部分匹配值的生成方法,就可以写程序了:

  1. void cal_next(char *str, int *next, int len)
  2. {
  3. next[0] = -1;//next[0]初始化为-1,-1表示不存在相同的最大前缀和最大后缀
  4. int k = -1;//k初始化为-1
  5. for (int q = 1; q <= len-1; q++)
  6. {
  7. while (k > -1 && str[k + 1] != str[q])//如果下一个不同,那么k就变成next[k],注意next[k]是小于k的,无论k取任何值。
  8. {
  9. k = next[k];//往前回溯
  10. }
  11. if (str[k + 1] == str[q])//如果相同,k++
  12. {
  13. k = k + 1;
  14. }
  15. next[q] = k;//这个是把算的k的值(就是相同的最大前缀和最大后缀长)赋给next[q]
  16. }
  17. }
  18. int KMP(char *str, int slen, char *ptr, int plen)
  19. {
  20. int *next = new int[plen];
  21. cal_next(ptr, next, plen);//计算next数组
  22. int k = -1;
  23. for (int i = 0; i < slen; i++)
  24. {
  25. while (k >-1&& ptr[k + 1] != str[i])//ptr和str不匹配,且k>-1(表示ptr和str有部分匹配)
  26. k = next[k];//往前回溯
  27. if (ptr[k + 1] == str[i])
  28. k = k + 1;
  29. if (k == plen-1)//说明k移动到ptr的最末端
  30. {
  31. //cout << "在位置" << i-plen+1<< endl;
  32. //k = -1;//重新初始化,寻找下一个
  33. //i = i - plen + 1;//i定位到该位置,外层for循环i++可以继续找下一个(这里默认存在两个匹配字符串可以部分重叠),感谢评论中同学指出错误。
  34. return i-plen+1;//返回相应的位置
  35. }
  36. }
  37. return -1;
  38. }

测试程序:

  1. char *str = "bacbababadababacambabacaddababacasdsd";
  2. char *ptr = "ababaca";
  3. int a = KMP(str, 36, ptr, 7);
  4. return 0;

三、KMS的理解

当搜索词已经匹配了6个字符,最后一个字符不匹配时

传统的做法是移动一位,然后重新比较

可是KMS却是直接移动了6 - 2 =4位:

这是为什么呢?

怎么保证的移动1位、移动2位、移动3位时遇到的字符串都不匹配,所以KMS能直接移动4位,再去比较呢?

其实非常简单,这就是字符串沿中间折叠后,首尾字符重叠个数(也就是部分匹配值)的作用所在。

下面我举个例子就懂了:

比如要在字符串“123456789”中找“4568”

当匹配到上图的位置时,发现字符“7”和字符“8”不匹配,于是传统的方法是向后移动一位:

而KMS的移动距离用公式计算为:3-0=3位

KMS算法把“4568”中已经匹配过的“456”,直接跳过去了。为什么这里能直接把匹配过的字符串“456“跳过去呢?

下面我们来讨论,我们把已经匹配过的字符分5种情况:(以3个字符来举例)

1)、已经匹配过的字符串中,每个字符都不相同,如”456“

    如果是这种情况,毫无疑问是可以直接把已匹配的字符跳过去的。

2)、已经匹配过的字符串中,每个字符都相同,如”444“

    这种情况,最多只能移动1位,比如下面的例子:

3)、已经匹配过的字符串中,首尾字符相同,如”454“

    这种情况,移动位数要大于1,小于等于2。比如:

移动一位后:

首字符会和中间的字符对齐,他们不相同。再移动一位,就可能完全匹配了,因此移动位数最多为2。

4)、已经匹配过的字符串中,首两个字符相同,如”445“

移动一位:

最后两个字符会和首两个字符对齐,他们一定不相同。

移动两位:

最后一个字符会和首字符对齐,他们一定不相同。因此移动位数最少为3。

5)、已经匹配过的字符串中,尾两个字符相同,如”455“

同理,移动位数最少为3。

        

        经过上面的例子分析,可以看出KMS算法就是对已经匹配过的字符串再次利用来提高效率的。

        KMS算法可以一句话总结为:已经匹配过的字符串如果首尾有相同的字母,就要少移动相应的位数,否则,已经匹配了多少个字符,就可以移动多少位。

这篇关于字符串匹配的KMP算法和C语言代码,不需要思考就能理解</h1>的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1011115

相关文章

基于 HTML5 Canvas 实现图片旋转与下载功能(完整代码展示)

《基于HTML5Canvas实现图片旋转与下载功能(完整代码展示)》本文将深入剖析一段基于HTML5Canvas的代码,该代码实现了图片的旋转(90度和180度)以及旋转后图片的下载... 目录一、引言二、html 结构分析三、css 样式分析四、JavaScript 功能实现一、引言在 Web 开发中,

Python如何去除图片干扰代码示例

《Python如何去除图片干扰代码示例》图片降噪是一个广泛应用于图像处理的技术,可以提高图像质量和相关应用的效果,:本文主要介绍Python如何去除图片干扰的相关资料,文中通过代码介绍的非常详细,... 目录一、噪声去除1. 高斯噪声(像素值正态分布扰动)2. 椒盐噪声(随机黑白像素点)3. 复杂噪声(如伪

Java Spring ApplicationEvent 代码示例解析

《JavaSpringApplicationEvent代码示例解析》本文解析了Spring事件机制,涵盖核心概念(发布-订阅/观察者模式)、代码实现(事件定义、发布、监听)及高级应用(异步处理、... 目录一、Spring 事件机制核心概念1. 事件驱动架构模型2. 核心组件二、代码示例解析1. 事件定义

Python实例题之pygame开发打飞机游戏实例代码

《Python实例题之pygame开发打飞机游戏实例代码》对于python的学习者,能够写出一个飞机大战的程序代码,是不是感觉到非常的开心,:本文主要介绍Python实例题之pygame开发打飞机... 目录题目pygame-aircraft-game使用 Pygame 开发的打飞机游戏脚本代码解释初始化部

Java中Map.Entry()含义及方法使用代码

《Java中Map.Entry()含义及方法使用代码》:本文主要介绍Java中Map.Entry()含义及方法使用的相关资料,Map.Entry是Java中Map的静态内部接口,用于表示键值对,其... 目录前言 Map.Entry作用核心方法常见使用场景1. 遍历 Map 的所有键值对2. 直接修改 Ma

Springboot3+将ID转为JSON字符串的详细配置方案

《Springboot3+将ID转为JSON字符串的详细配置方案》:本文主要介绍纯后端实现Long/BigIntegerID转为JSON字符串的详细配置方案,s基于SpringBoot3+和Spr... 目录1. 添加依赖2. 全局 Jackson 配置3. 精准控制(可选)4. OpenAPI (Spri

Go语言中泄漏缓冲区的问题解决

《Go语言中泄漏缓冲区的问题解决》缓冲区是一种常见的数据结构,常被用于在不同的并发单元之间传递数据,然而,若缓冲区使用不当,就可能引发泄漏缓冲区问题,本文就来介绍一下问题的解决,感兴趣的可以了解一下... 目录引言泄漏缓冲区的基本概念代码示例:泄漏缓冲区的产生项目场景:Web 服务器中的请求缓冲场景描述代码

Go语言如何判断两张图片的相似度

《Go语言如何判断两张图片的相似度》这篇文章主要为大家详细介绍了Go语言如何中实现判断两张图片的相似度的两种方法,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 在介绍技术细节前,我们先来看看图片对比在哪些场景下可以用得到:图片去重:自动删除重复图片,为存储空间"瘦身"。想象你是一个

Go语言中Recover机制的使用

《Go语言中Recover机制的使用》Go语言的recover机制通过defer函数捕获panic,实现异常恢复与程序稳定性,具有一定的参考价值,感兴趣的可以了解一下... 目录引言Recover 的基本概念基本代码示例简单的 Recover 示例嵌套函数中的 Recover项目场景中的应用Web 服务器中

使用Python实现base64字符串与图片互转的详细步骤

《使用Python实现base64字符串与图片互转的详细步骤》要将一个Base64编码的字符串转换为图片文件并保存下来,可以使用Python的base64模块来实现,这一过程包括解码Base64字符串... 目录1. 图片编码为 Base64 字符串2. Base64 字符串解码为图片文件3. 示例使用注意