字符串匹配算法之BF与KMP算法

2024-04-08 23:52
文章标签 算法 字符串 匹配 kmp bf

本文主要是介绍字符串匹配算法之BF与KMP算法,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

BF算法(暴力匹配算法)

KMP算法

核心思想:

next数组

next数组的优化


BF算法(暴力匹配算法)

#include <assert.h>
int BF(const char* str, const char* sub)
{assert(str != NULL && sub != NULL);if (str == NULL || sub == NULL){return -1;}int i = 0;int j = 0;int strLen = strlen(str);int subLen = strlen(sub);while (i < strLen && j < subLen){if (str[i] == sub[j]){i++;j++;}else{i = i - j + 1;  //主串从上次开始匹配的下一个位置开始匹配j = 0; //子串每次从头开始匹配}}if (j >= subLen){return i - j; //返回子串在主串中首次出现的首位置的下标}return -1; //没有匹配返回-1
}int main()
{printf("%d\n", BF("ababcabcdabcde", "abcd")); //5printf("%d\n", BF("ababcabcdabcde", "abcde")); //9printf("%d\n", BF("ababcabcdabcde", "abcdef")); //-1return 0;
}

KMP算法

核心思想:

KMP算法是在BF算法基础上的优化,BF算法中匹配不成功时i和j都要回退,而KMP算法的核心就是匹配失败时,i不回退,而j回退到特定的位置(不一定是0号位置了),然后继续匹配!

问题:为啥i可以不回退, j也不一定非要回退到0号位置??

有了上面的分析,我们的目标就已经很明确了,就是要计算子串的每个位置匹配失败时,j应该回退到哪一个位置,于是就有了我们的next数组!

next数组

next[i] 记录的就是子串匹配到 i 位置时 匹配失败后 i 应该回退的下标

而next数组如何求解呢??? 比如next数组中 i 下标对应的值是几呢??? 那我们只需要看子串中 [0, i-1]这段区间前缀 和 后缀相等的字符串的最长长度, 假如是k, 那么next[i] = k

子串的第一个位置和第二个位置匹配失败时,该位置之前绝不可能用公共子串,所以next[0]和next[1]都应该是0, 但是为了方便后续代码处理,我们将next[0]置成-1

我们现在已经明白了next数组的做用和求法,但问题是上面的next数组是我们肉眼观察求得的,可是计算机并没有上帝视角,如何编程求得next数组呢???

假设已知next[i] = k, 我们能否求得next[i+1]等于多少呢??? 如果可以求出来,由于next[0]和next[1]都是已知的,所以我们只需要从第三个位置开始for循环即可求得next数组~

问题: 已知next[i] = k, 求next[i+1] = 多少??

1.假设p[i] == p[k], 那么next[i+1] = k+1   (p是patten, 指的是子串, 也叫模式串)

证明:

next[i] = k, 说明 p[0] 到 p[k-1] 与 p[x] 到 p[i-1] 这两段字符串是一样的,根据两段字符串长度一样可以求得x,  (k-1-0)+1 = (i-1-x)+1, 求得x=i-k, 所以 p[0]...[k-1] == p[i-k]...p[i-1]

而p[i] == p[k], 则 p[0]...[k] == p[i-k]...p[i], 即 next[i+1] = k+1

2.假设p[i] != p[k], 此时需要做的就是让k回退即可,只需要让k = next[k],一直回退到 p[i] == p[k], 此时next[i+1] = k+1了!!!

如果回退到了0位置,p[i] 仍然不等于 != p[k],  k再 = next[k], k就 == -1了, 此时如果再判断 p[i] == p[k] 就会越界! 所以如果k == -1了,表明next[i+1]也就是0,刚好是k+1, 这就是为啥把next[0]设置成-1的原因, 然后i++, k++继续填充next数组即可

到此为止,我们就把kmp算法的核心都讲解完了,重点就是kmp算法的核心思想与next数组的原理与求法

#include <assert.h>
#include <stdio.h>
void GetNext(int* next, const char* sub)
{int lensub = strlen(sub);next[0] = -1;next[1] = 0;int i = 2;//下一项int k = 0;//前一项的Kwhile (i < lensub)//next数组还没有遍历完{//注意,讲解原理时我们假设已知next[i],求next[i+1], 但写代码时next[i]是我们要求解的,因此已知next[i-1]if ((k == -1) || sub[k] == sub[i - 1]) {next[i] = k + 1;i++;k++;}else{k = next[k]; //k回退}}
}int KMP(const char* s, const char* sub, int pos) //pos表示从主串的pos位置开始匹配
{int i = pos;int j = 0;int lens = strlen(s);int lensub = strlen(sub);int* next = (int*)malloc(lensub * sizeof(int));//和子串一样长assert(next != NULL);GetNext(next, sub);while (i < lens && j < lensub){if ((j == -1) || (s[i] == sub[j])) {i++;j++;}else{//如果子串的第一个位置字符就和主串不匹配,那么j就会直接变成-1,然后进入if, 此时让j++来到0号位置,i++来到下一个位置继续匹配即可j = next[j]; }}free(next);if (j >= lensub){return i - j;}else{return -1;}
}int main()
{const char* str = "ababcabcdabcde";const char* sub = "abcd";printf("%d\n", KMP(str, sub, 0)); //5return 0;
}
next数组的优化

如果子串中,有大量的重复元素时,next数组就可以优化,因为假设6号下标匹配失败,回退到next[6]也就是5号下标, 此时字符仍然是'a', 依旧会匹配失败,还需要继续回退!!!

 所以我们可以将next数组优化成nextval数组,nextval数组可以根据next数组求得

1.回退到的位置和当前字符一样,就写回退那个位置的nextval值

2.回退到的位置和当前字符不一样,就写当前字符原来的next值

这篇关于字符串匹配算法之BF与KMP算法的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

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

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

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

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

Java中的雪花算法Snowflake解析与实践技巧

《Java中的雪花算法Snowflake解析与实践技巧》本文解析了雪花算法的原理、Java实现及生产实践,涵盖ID结构、位运算技巧、时钟回拨处理、WorkerId分配等关键点,并探讨了百度UidGen... 目录一、雪花算法核心原理1.1 算法起源1.2 ID结构详解1.3 核心特性二、Java实现解析2.

MySQL 获取字符串长度及注意事项

《MySQL获取字符串长度及注意事项》本文通过实例代码给大家介绍MySQL获取字符串长度及注意事项,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录mysql 获取字符串长度详解 核心长度函数对比⚠️ 六大关键注意事项1. 字符编码决定字节长度2

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

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

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

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

golang float和科学计数法转字符串的实现方式

《golangfloat和科学计数法转字符串的实现方式》:本文主要介绍golangfloat和科学计数法转字符串的实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望... 目录golang float和科学计数法转字符串需要对float转字符串做处理总结golang float

Python如何判断字符串中是否包含特殊字符并替换

《Python如何判断字符串中是否包含特殊字符并替换》这篇文章主要为大家详细介绍了如何使用Python实现判断字符串中是否包含特殊字符并使用空字符串替换掉,文中的示例代码讲解详细,感兴趣的小伙伴可以了... 目录python判断字符串中是否包含特殊字符方法一:使用正则表达式方法二:手动检查特定字符Pytho

MySQL 字符串截取函数及用法详解

《MySQL字符串截取函数及用法详解》在MySQL中,字符串截取是常见的操作,主要用于从字符串中提取特定部分,MySQL提供了多种函数来实现这一功能,包括LEFT()、RIGHT()、SUBST... 目录mysql 字符串截取函数详解RIGHT(str, length):从右侧截取指定长度的字符SUBST

Python将字符串转换为小写字母的几种常用方法

《Python将字符串转换为小写字母的几种常用方法》:本文主要介绍Python中将字符串大写字母转小写的四种方法:lower()方法简洁高效,手动ASCII转换灵活可控,str.translate... 目录一、使用内置方法 lower()(最简单)二、手动遍历 + ASCII 码转换三、使用 str.tr