KMP(Knuth-Morris-Pratt)算法详解及C++代码实现

2024-06-02 09:36

本文主要是介绍KMP(Knuth-Morris-Pratt)算法详解及C++代码实现,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

在计算机科学中,字符串匹配是一个基础且重要的任务。给定一个主字符串(也称为文本)和一个模式字符串(也称为词),字符串匹配算法的任务是在主字符串中查找与模式字符串相同的子串,并返回其位置。Knuth-Morris-Pratt(KMP)算法是一种高效的字符串匹配算法,由D.E. Knuth、J.H. Morris和V.R. Pratt联合提出。该算法通过减少不必要的字符比较次数,从而提高了字符串匹配的效率。

一、KMP算法的基本思想

KMP算法的基本思想利用已经部分匹配这个有效信息,保持主字符串(记为S)的指针不回溯,通过修改模式字符串(记为P)的指针位置,让模式字符串尽量地“滑动”,使得每次回溯都能比暴力匹配算法减少更多的比较次数。为了达到这个目的,KMP算法需要预处理模式字符串,得到一个“部分匹配表”(也称为“失败函数表”或“跳转表”),该表记录了当模式字符串中的某个字符与主字符串的当前字符不匹配时,模式字符串应该向前移动多少位。

二、KMP算法的实现步骤

  1. 预处理部分匹配表

    • 初始化一个数组next,用于存储部分匹配表。
    • j = 0k = -1next[0] = -1
    • j < 模式字符串长度时,进行循环:
      • 如果k == -1模式字符串[j] == 模式字符串[k],则j++k++next[j] = k
      • 否则,k = next[k]
    • 预处理完成后,next数组就存储了模式字符串的部分匹配表。
  2. 字符串匹配

    • 初始化两个指针ij,分别指向主字符串和模式字符串的起始位置。
    • i < 主字符串长度j < 模式字符串长度时,进行循环:
      • 如果主字符串[i] == 模式字符串[j],则i++j++
      • 如果j已经等于模式字符串的长度,说明找到了一个匹配,输出匹配位置i - j,并将j重置为0,继续下一轮匹配。
      • 如果主字符串[i] != 模式字符串[j],则根据next[j]的值更新j,即j = next[j]
    • 如果循环结束且没有找到匹配,则返回未找到匹配的标识。

三、KMP算法的时间复杂度

KMP算法的时间复杂度主要取决于预处理部分匹配表的时间和实际的匹配过程。预处理部分匹配表的时间复杂度为O(m),其中m为模式字符串的长度。实际的匹配过程的时间复杂度为O(n+m),其中n为主字符串的长度。因此,KMP算法的总时间复杂度为O(n+m)。

四、KMP算法的C++代码实现

#include <iostream>
#include <vector>
#include <string>using namespace std;// 获取部分匹配表
vector<int> getNext(const string& pattern) {int m = pattern.length();vector<int> next(m, 0);int j = 0, k = -1;next[0] = -1;while (j < m - 1) {if (k == -1 || pattern[j] == pattern[k]) {++j;++k;next[j] = k;} else {k = next[k];}}return next;
}// KMP算法
int kmpSearch(const string& str, const string& pattern) {int n = str.length();int m = pattern.length();vector<int> next = getNext(pattern);int i = 0, j = 0;while (i < n && j < m) {if (j == -1 || str[i] == pattern[j]) {++i;++j;} else {j = next[j];}}if (j == m) {return i - j; // 匹配成功,返回匹配开始的位置} else {return -1; // 匹配失败,返回-1}  
}  // 主函数  
int main() {  string str = "ABABDABACDABABCABAB";  string pattern = "ABABCABAB";  int pos = kmpSearch(str, pattern);  if (pos != -1) {  cout << "Pattern found at position: " << pos << endl;  } else {  cout << "Pattern not found." << endl;  }  return 0;  
}

五、代码详解

1、getNext函数:该函数用于获取模式字符串的部分匹配表。在循环中,我们比较当前字符pattern[j]和pattern[k],如果它们相等,则继续向后移动两个指针;如果不相等,则将k回溯到next[k]的值,再次进行比较。需要注意的是,当pattern[j]和pattern[k]相等时,我们需要检查pattern[j+1]和pattern[k+1]是否也相等,以确定next[j+1]的值。
2、kmpSearch函数:该函数实现了KMP算法的核心逻辑。我们使用两个指针i和j分别指向主字符串和模式字符串的当前位置。在循环中,我们比较str[i]和pattern[j],如果它们相等,则继续向后移动两个指针;如果不相等,则将j回溯到next[j]的值,再次进行比较。当j等于模式字符串的长度时,说明找到了一个匹配,返回匹配开始的位置;如果循环结束且没有找到匹配,则返回-1。
3、main函数:在主函数中,我们定义了一个主字符串str和一个模式字符串pattern,并调用kmpSearch函数进行匹配。根据返回的结果,我们输出相应的信息。

六、总结

KMP算法是一种高效的字符串匹配算法,它通过预处理模式字符串的部分匹配表来减少不必要的字符比较次数。在C++中实现KMP算法时,需要注意部分匹配表的获取和字符串匹配的逻辑。通过上面的代码示例和详解,我们可以更好地理解KMP算法的实现原理和应用方法。

这篇关于KMP(Knuth-Morris-Pratt)算法详解及C++代码实现的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

关于C++中的虚拟继承的一些总结(虚拟继承,覆盖,派生,隐藏)

1.为什么要引入虚拟继承 虚拟继承是多重继承中特有的概念。虚拟基类是为解决多重继承而出现的。如:类D继承自类B1、B2,而类B1、B2都继承自类A,因此在类D中两次出现类A中的变量和函数。为了节省内存空间,可以将B1、B2对A的继承定义为虚拟继承,而A就成了虚拟基类。实现的代码如下: class A class B1:public virtual A; class B2:pu

C++对象布局及多态实现探索之内存布局(整理的很多链接)

本文通过观察对象的内存布局,跟踪函数调用的汇编代码。分析了C++对象内存的布局情况,虚函数的执行方式,以及虚继承,等等 文章链接:http://dev.yesky.com/254/2191254.shtml      论C/C++函数间动态内存的传递 (2005-07-30)   当你涉及到C/C++的核心编程的时候,你会无止境地与内存管理打交道。 文章链接:http://dev.yesky

C++的模板(八):子系统

平常所见的大部分模板代码,模板所传的参数类型,到了模板里面,或实例化为对象,或嵌入模板内部结构中,或在模板内又派生了子类。不管怎样,最终他们在模板内,直接或间接,都实例化成对象了。 但这不是唯一的用法。试想一下。如果在模板内限制调用参数类型的构造函数会发生什么?参数类的对象在模板内无法构造。他们只能从模板的成员函数传入。模板不保存这些对象或者只保存他们的指针。因为构造函数被分离,这些指针在模板外

C++工程编译链接错误汇总VisualStudio

目录 一些小的知识点 make工具 可以使用windows下的事件查看器崩溃的地方 dumpbin工具查看dll是32位还是64位的 _MSC_VER .cc 和.cpp 【VC++目录中的包含目录】 vs 【C/C++常规中的附加包含目录】——头文件所在目录如何怎么添加,添加了以后搜索头文件就会到这些个路径下搜索了 include<> 和 include"" WinMain 和

uniapp接入微信小程序原生代码配置方案(优化版)

uniapp项目需要把微信小程序原生语法的功能代码嵌套过来,无需把原生代码转换为uniapp,可以配置拷贝的方式集成过来 1、拷贝代码包到src目录 2、vue.config.js中配置原生代码包直接拷贝到编译目录中 3、pages.json中配置分包目录,原生入口组件的路径 4、manifest.json中配置分包,使用原生组件 5、需要把原生代码包里的页面修改成组件的方

C/C++的编译和链接过程

目录 从源文件生成可执行文件(书中第2章) 1.Preprocessing预处理——预处理器cpp 2.Compilation编译——编译器cll ps:vs中优化选项设置 3.Assembly汇编——汇编器as ps:vs中汇编输出文件设置 4.Linking链接——链接器ld 符号 模块,库 链接过程——链接器 链接过程 1.简单链接的例子 2.链接过程 3.地址和

C++必修:模版的入门到实践

✨✨ 欢迎大家来到贝蒂大讲堂✨✨ 🎈🎈养成好习惯,先赞后看哦~🎈🎈 所属专栏:C++学习 贝蒂的主页:Betty’s blog 1. 泛型编程 首先让我们来思考一个问题,如何实现一个交换函数? void swap(int& x, int& y){int tmp = x;x = y;y = tmp;} 相信大家很快就能写出上面这段代码,但是如果要求这个交换函数支持字符型

公共筛选组件(二次封装antd)支持代码提示

如果项目是基于antd组件库为基础搭建,可使用此公共筛选组件 使用到的库 npm i antdnpm i lodash-esnpm i @types/lodash-es -D /components/CommonSearch index.tsx import React from 'react';import { Button, Card, Form } from 'antd'

17.用300行代码手写初体验Spring V1.0版本

1.1.课程目标 1、了解看源码最有效的方式,先猜测后验证,不要一开始就去调试代码。 2、浓缩就是精华,用 300行最简洁的代码 提炼Spring的基本设计思想。 3、掌握Spring框架的基本脉络。 1.2.内容定位 1、 具有1年以上的SpringMVC使用经验。 2、 希望深入了解Spring源码的人群,对 Spring有一个整体的宏观感受。 3、 全程手写实现SpringM

十四、观察者模式与访问者模式详解

21.观察者模式 21.1.课程目标 1、 掌握观察者模式和访问者模式的应用场景。 2、 掌握观察者模式在具体业务场景中的应用。 3、 了解访问者模式的双分派。 4、 观察者模式和访问者模式的优、缺点。 21.2.内容定位 1、 有 Swing开发经验的人群更容易理解观察者模式。 2、 访问者模式被称为最复杂的设计模式。 21.3.观察者模式 观 察 者 模 式 ( Obser