字符串匹配算法之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

相关文章

JSON字符串转成java的Map对象详细步骤

《JSON字符串转成java的Map对象详细步骤》:本文主要介绍如何将JSON字符串转换为Java对象的步骤,包括定义Element类、使用Jackson库解析JSON和添加依赖,文中通过代码介绍... 目录步骤 1: 定义 Element 类步骤 2: 使用 Jackson 库解析 jsON步骤 3: 添

Java 字符数组转字符串的常用方法

《Java字符数组转字符串的常用方法》文章总结了在Java中将字符数组转换为字符串的几种常用方法,包括使用String构造函数、String.valueOf()方法、StringBuilder以及A... 目录1. 使用String构造函数1.1 基本转换方法1.2 注意事项2. 使用String.valu

Python中的随机森林算法与实战

《Python中的随机森林算法与实战》本文详细介绍了随机森林算法,包括其原理、实现步骤、分类和回归案例,并讨论了其优点和缺点,通过面向对象编程实现了一个简单的随机森林模型,并应用于鸢尾花分类和波士顿房... 目录1、随机森林算法概述2、随机森林的原理3、实现步骤4、分类案例:使用随机森林预测鸢尾花品种4.1

python修改字符串值的三种方法

《python修改字符串值的三种方法》本文主要介绍了python修改字符串值的三种方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学... 目录第一种方法:第二种方法:第三种方法:在python中,字符串对象是不可变类型,所以我们没办法直接

JAVA中整型数组、字符串数组、整型数和字符串 的创建与转换的方法

《JAVA中整型数组、字符串数组、整型数和字符串的创建与转换的方法》本文介绍了Java中字符串、字符数组和整型数组的创建方法,以及它们之间的转换方法,还详细讲解了字符串中的一些常用方法,如index... 目录一、字符串、字符数组和整型数组的创建1、字符串的创建方法1.1 通过引用字符数组来创建字符串1.2

C#中字符串分割的多种方式

《C#中字符串分割的多种方式》在C#编程语言中,字符串处理是日常开发中不可或缺的一部分,字符串分割是处理文本数据时常用的操作,它允许我们将一个长字符串分解成多个子字符串,本文给大家介绍了C#中字符串分... 目录1. 使用 string.Split2. 使用正则表达式 (Regex.Split)3. 使用

Java中JSON字符串反序列化(动态泛型)

《Java中JSON字符串反序列化(动态泛型)》文章讨论了在定时任务中使用反射调用目标对象时处理动态参数的问题,通过将方法参数存储为JSON字符串并进行反序列化,可以实现动态调用,然而,这种方式容易导... 需求:定时任务扫描,反射调用目标对象,但是,方法的传参不是固定的。方案一:将方法参数存成jsON字

不懂推荐算法也能设计推荐系统

本文以商业化应用推荐为例,告诉我们不懂推荐算法的产品,也能从产品侧出发, 设计出一款不错的推荐系统。 相信很多新手产品,看到算法二字,多是懵圈的。 什么排序算法、最短路径等都是相对传统的算法(注:传统是指科班出身的产品都会接触过)。但对于推荐算法,多数产品对着网上搜到的资源,都会无从下手。特别当某些推荐算法 和 “AI”扯上关系后,更是加大了理解的难度。 但,不了解推荐算法,就无法做推荐系

康拓展开(hash算法中会用到)

康拓展开是一个全排列到一个自然数的双射(也就是某个全排列与某个自然数一一对应) 公式: X=a[n]*(n-1)!+a[n-1]*(n-2)!+...+a[i]*(i-1)!+...+a[1]*0! 其中,a[i]为整数,并且0<=a[i]<i,1<=i<=n。(a[i]在不同应用中的含义不同); 典型应用: 计算当前排列在所有由小到大全排列中的顺序,也就是说求当前排列是第

csu 1446 Problem J Modified LCS (扩展欧几里得算法的简单应用)

这是一道扩展欧几里得算法的简单应用题,这题是在湖南多校训练赛中队友ac的一道题,在比赛之后请教了队友,然后自己把它a掉 这也是自己独自做扩展欧几里得算法的题目 题意:把题意转变下就变成了:求d1*x - d2*y = f2 - f1的解,很明显用exgcd来解 下面介绍一下exgcd的一些知识点:求ax + by = c的解 一、首先求ax + by = gcd(a,b)的解 这个