字符串匹配的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

相关文章

Java Spring的依赖注入理解及@Autowired用法示例详解

《JavaSpring的依赖注入理解及@Autowired用法示例详解》文章介绍了Spring依赖注入(DI)的概念、三种实现方式(构造器、Setter、字段注入),区分了@Autowired(注入... 目录一、什么是依赖注入(DI)?1. 定义2. 举个例子二、依赖注入的几种方式1. 构造器注入(Con

详解MySQL中JSON数据类型用法及与传统JSON字符串对比

《详解MySQL中JSON数据类型用法及与传统JSON字符串对比》MySQL从5.7版本开始引入了JSON数据类型,专门用于存储JSON格式的数据,本文将为大家简单介绍一下MySQL中JSON数据类型... 目录前言基本用法jsON数据类型 vs 传统JSON字符串1. 存储方式2. 查询方式对比3. 索引

Go语言编译环境设置教程

《Go语言编译环境设置教程》Go语言支持高并发(goroutine)、自动垃圾回收,编译为跨平台二进制文件,云原生兼容且社区活跃,开发便捷,内置测试与vet工具辅助检测错误,依赖模块化管理,提升开发效... 目录Go语言优势下载 Go  配置编译环境配置 GOPROXYIDE 设置(VS Code)一些基本

Python实现MQTT通信的示例代码

《Python实现MQTT通信的示例代码》本文主要介绍了Python实现MQTT通信的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一... 目录1. 安装paho-mqtt库‌2. 搭建MQTT代理服务器(Broker)‌‌3. pytho

MySQL字符串常用函数详解

《MySQL字符串常用函数详解》本文给大家介绍MySQL字符串常用函数,本文结合实例代码给大家介绍的非常详细,对大家学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录mysql字符串常用函数一、获取二、大小写转换三、拼接四、截取五、比较、反转、替换六、去空白、填充MySQL字符串常用函数一、

MySQL进行数据库审计的详细步骤和示例代码

《MySQL进行数据库审计的详细步骤和示例代码》数据库审计通过触发器、内置功能及第三方工具记录和监控数据库活动,确保安全、完整与合规,Java代码实现自动化日志记录,整合分析系统提升监控效率,本文给大... 目录一、数据库审计的基本概念二、使用触发器进行数据库审计1. 创建审计表2. 创建触发器三、Java

深入理解Go语言中二维切片的使用

《深入理解Go语言中二维切片的使用》本文深入讲解了Go语言中二维切片的概念与应用,用于表示矩阵、表格等二维数据结构,文中通过示例代码介绍的非常详细,需要的朋友们下面随着小编来一起学习学习吧... 目录引言二维切片的基本概念定义创建二维切片二维切片的操作访问元素修改元素遍历二维切片二维切片的动态调整追加行动态

Python中反转字符串的常见方法小结

《Python中反转字符串的常见方法小结》在Python中,字符串对象没有内置的反转方法,然而,在实际开发中,我们经常会遇到需要反转字符串的场景,比如处理回文字符串、文本加密等,因此,掌握如何在Pyt... 目录python中反转字符串的方法技术背景实现步骤1. 使用切片2. 使用 reversed() 函

Go语言中make和new的区别及说明

《Go语言中make和new的区别及说明》:本文主要介绍Go语言中make和new的区别及说明,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1 概述2 new 函数2.1 功能2.2 语法2.3 初始化案例3 make 函数3.1 功能3.2 语法3.3 初始化

MySQL查询JSON数组字段包含特定字符串的方法

《MySQL查询JSON数组字段包含特定字符串的方法》在MySQL数据库中,当某个字段存储的是JSON数组,需要查询数组中包含特定字符串的记录时传统的LIKE语句无法直接使用,下面小编就为大家介绍两种... 目录问题背景解决方案对比1. 精确匹配方案(推荐)2. 模糊匹配方案参数化查询示例使用场景建议性能优