【字符串匹配】BF与KMP算法

news2024/11/16 19:37:57

一、字符串匹配问题

字符串匹配问题是指在一个主文本字符串中查找一个指定的模式字符串,并确定模式字符串在主文本中出现的位置。这个问题在计算机科学中非常常见,尤其是在文本处理、数据搜索和生物信息学等领域。

字符串匹配问题通常涉及到以下几个方面:

  1. 模式识别:识别主文本中是否存在模式字符串,以及模式字符串出现的次数和位置。
  2. 算法效率:由于字符串可能非常长,因此匹配算法的效率至关重要。不同的算法有不同的时间复杂度,选择合适的算法可以显著提高匹配速度。
  3. 实际应用:字符串匹配不仅仅局限于文字的查找,还可以应用于更复杂的场景,如DNA序列匹配、网络安全中的入侵检测等。

此外,在实际应用中,字符串匹配问题可能会有更多的变化和挑战,例如处理包含特殊字符的字符串,或者在不完全匹配的情况下找到最接近的匹配项。这些问题都需要根据具体情况选择合适的算法和策略来解决。

总的来说,字符串匹配问题是计算机科学中的一个基础且重要的问题,它不仅关系到算法的设计和优化,也直接影响到许多实际应用的效率和准确性。

二、暴力解法

Brute Force(暴力匹配)算法是一种简单的字符串匹配方法,它通过逐个比较主文本字符串和模式字符串的字符来查找匹配项。下面是使用C++实现BF算法的代码示例:

#include <iostream>
#include <string>

int bruteForce(const std::string& text, const std::string& pattern) {
    int n = text.length();
    int m = pattern.length();

    for (int i = 0; i <= n - m; ++i) {
        int j;
        for (j = 0; j < m; ++j) {
            if (text[i + j] != pattern[j]) {
                break;
            }
        }
        if (j == m) {
            return i; // 找到匹配项,返回起始位置
        }
    }
    return -1; // 未找到匹配项
}

int main() {
    std::string text = "Hello, world!";
    std::string pattern = "world";

    int position = bruteForce(text, pattern);
    if (position != -1) {
        std::cout << "Pattern found at position: " << position << std::endl;
    } else {
        std::cout << "Pattern not found." << std::endl;
    }

    return 0;
}

上述代码中,bruteForce函数接受两个参数:主文本字符串text和模式字符串pattern。它使用两层循环进行匹配,外层循环遍历主文本字符串,内层循环逐个比较字符。如果在内层循环中发现不匹配的字符,则跳出内层循环并继续外层循环的下一次迭代。如果内层循环完全执行完毕,说明找到了匹配项,返回匹配的起始位置。如果外层循环结束后仍未找到匹配项,则返回-1表示未找到匹配项。

main函数中,我们定义了一个示例的主文本字符串和模式字符串,并调用bruteForce函数进行匹配。根据返回的位置值,我们可以判断是否找到匹配项,并输出相应的结果。

需要注意的是,BF算法的时间复杂度较高,为O((n-m+1)*m),其中n是主文本字符串的长度,m是模式字符串的长度。因此,对于较长的字符串或频繁的匹配操作,可能需要选择更高效的算法来提高性能。

三、暴力解法的缺点

暴力算法(Brute Force)在字符串匹配中的缺点主要包括以下几点:

  1. 时间复杂度高:暴力算法的时间复杂度为O((n-m+1)*m),其中n是主文本字符串的长度,m是模式字符串的长度。当主文本和模式字符串都非常长时,所需的比较次数会显著增加,导致匹配过程变得非常耗时。
  2. 效率低下:由于暴力算法没有充分利用模式字符串的信息,每次匹配都需要从头开始逐个比较字符,因此效率较低。在最坏情况下,即使模式字符串与主文本完全不匹配,也需要进行大量的比较操作。
  3. 不适用于频繁匹配的场景:由于暴力算法的效率较低,因此在需要频繁进行字符串匹配的场景中(如文本编辑器的查找功能),使用暴力算法可能会导致性能瓶颈。
  4. 无法利用部分匹配信息:暴力算法在遇到不匹配的字符时,会立即停止当前位置的比较,并移动到下一个位置重新开始比较。然而,这种方法无法利用已经匹配的部分信息,可能导致不必要的比较。

相比之下,其他更高效的字符串匹配算法(如Knuth-Morris-Pratt算法、Boyer-Moore算法等)通过预处理模式字符串或使用启发式规则来减少不必要的比较,从而提高了匹配效率。在实际应用中,选择合适的算法应根据具体的应用场景和性能要求来决定。

四、KMP算法

KMP算法是一种高效的字符串匹配算法,由D.E. Knuth、J.H. Morris和V.R. Pratt于1977年发表

KMP算法相较于其他算法的优势在于它能够在不匹配发生时利用已经部分匹配的信息,避免重新检查之前已经匹配过的字符。该算法通过创建一个“部分匹配表”(也称为next数组),来记录模式串中前后最长公共子序列的长度。当发生不匹配时,算法可以利用这个表来决定模式串下一次移动的位置,从而减少不必要的比较,提高匹配效率。

在KMP算法中,有以下几个关键点:

  • 部分匹配表(Next Array):这个表保存了模式串中每个位置之前的子串的最长相等前后缀的长度。这有助于在发生不匹配时决定模式串应该向右滑动多少位。
  • 时间复杂度:KMP算法的时间复杂度为O(m+n),其中m是主文本字符串的长度,n是模式字符串的长度。这比暴力算法的O((n-m+1)*m)要低得多,尤其在大数据集上更为显著。
  • 空间效率:尽管KMP算法提高了时间效率,但它需要额外的空间来存储部分匹配表,这会增加一些空间复杂度。

此外,KMP算法特别适合于文本编辑器、编译器等需要快速字符串搜索的场景。在这些应用中,算法的效率至关重要,因为即使是微小的优化也可能对整体性能产生重大影响。

总的来说,KMP算法通过避免重复比较已匹配的字符,并利用部分匹配信息来确定下一次的比较位置,从而达到快速字符串匹配的目的。

五、代码实现KMP

整体流程

在进行匹配时会定义两个指针分别指向目标串和匹配串分别为i,j

两个指针开始遍历,当两个指针指向的元素相同时二者都往后继续走,如果此时不相等了则说明匹配失败,此时i指针不会回退,而是根据next数组让j指针回到合适的位置后i与j二者继续遍历,一直到i或者j走到了结尾

next数组的作用

由于在kmp算法中,i指针是不回退的,所以我们在j匹配失败时需要让j回退到合适的位置上,而next数组就记录了当j此时匹配失败时回退到next[j]的位置继续进行匹配,那next数组是如何进行维护的呢?

维护next数组

首先我们规定0下标为-1,1下标为0,也就是说如果在j=1时匹配失败由于next[1]=0所以j会回到0下标继续匹配,而后面3,4,5……下标的next值该如何确认呢,我们需要在sub串中的0到j位置找到这样的两个相同字串以sub[0] 开头且以sub[j - 1]结尾,如图我们能找到以a开头以b结尾的两个串,那么此时next[j]的值就是这个串的长度也就是2

遍历匹配

C++代码
#include <iostream>
#include <cmath>
#include <vector>
#include <algorithm>

using namespace std;

void getNext(vector<int>& next, string& sub) {
    next[0] = -1;
    int k = 0, i = 2, n = sub.size();
    while (i < n) {
        if (k == -1 || sub[i - 1] == sub[k]) {
            next[i++] = ++k;
        } else {
            k = next[k];
        }
    }
}

int kmp(string& str, string& sub, int pos) {
    int lenStr = str.size(), lenSub = sub.size();
    if (lenStr <= 0 || lenSub <= 0) return -1;
    vector<int> next(lenSub);
    getNext(next, sub);

    int i = pos, j = 0;
    while (i < lenStr && j < lenSub) {
        if (j == - 1 || str[i] == sub[j]) {
            i++, j++;
        } else {
            j = next[j];
        }
    }

    if (j <= lenSub) return i - j;
    else return -1;
}

int main() {
    string str = "cbababcabcd";
    string sub = "ababca";
    int k = kmp(str, sub, 0);
    cout << k;
    return 0;
}
Java代码
#include <iostream>
#include <cmath>
#include <vector>
#include <algorithm>

using namespace std;

void getNext(vector<int>& next, string& sub) {
    next[0] = -1;
    int k = 0, i = 2, n = sub.size();
    while (i < n) {
        if (k == -1 || sub[i - 1] == sub[k]) {
            next[i++] = ++k;
        } else {
            k = next[k];
        }
    }
}

int kmp(string& str, string& sub, int pos) {
    int lenStr = str.size(), lenSub = sub.size();
    if (lenStr <= 0 || lenSub <= 0) return -1;
    vector<int> next(lenSub);
    getNext(next, sub);

    int i = pos, j = 0;
    while (i < lenStr && j < lenSub) {
        if (j == - 1 || str[i] == sub[j]) {
            i++, j++;
        } else {
            j = next[j];
        }
    }

    if (j <= lenSub) return i - j;
    else return -1;
}

int main() {
    string str = "cbababcabcd";
    string sub = "ababca";
    int k = kmp(str, sub, 0);
    cout << k;
    return 0;
}
nextVal优化next数组

如果当前i位置值得元素与他next[i]位置得元素值相同,那么我们可以将这个位置得next[i]修改为他的next[next[i]],否则就不变

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1526652.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

JS原型和原型链的理解

原型链图&#xff0c;图中Parent是构造函数&#xff0c;p1是通过Parent实例化出来的一个对象 前置知识 js中对象和函数的关系&#xff0c;函数其实是对象的一种 函数、构造函数的区别&#xff0c;任何函数都可以作为构造函数&#xff0c;但是并不能将任意函数叫做构造函数&…

mybatis-plus 的saveBatch性能分析

Mybatis-Plus 的批量保存saveBatch 性能分析 目录 Mybatis-Plus 的批量保存saveBatch 性能分析背景批量保存的使用方案循环插入使用PreparedStatement 预编译优点&#xff1a;缺点&#xff1a; Mybatis-Plus 的saveBatchMybatis-Plus实现真正的批量插入自定义sql注入器定义通用…

从历年315曝光案例,看APP隐私合规安全

更多网络安全干货内容&#xff1a;点此获取 ——————— 随着移动互联网新兴技术的发展与普及&#xff0c;移动APP的应用渗透到人们的衣食住行方方面面&#xff0c;衍生出各类消费场景的同时&#xff0c;也带来了无数的个人隐私数据泄露、网络诈骗事件。 历年来&#xff…

29.网络游戏逆向分析与漏洞攻防-网络通信数据包分析工具-数据推测功能的算法实现

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 如果看不懂、不知道现在做的什么&#xff0c;那就跟着做完看效果 内容参考于&#xff1a;易道云信息技术研究院VIP课 上一个内容&#xff1a;28.数据推测结果…

修改/etc/resolve.conf重启NetworkManager之后自动还原

我ping 百度报错&#xff1a; [rootk8snode1 ~]# ping baidu.com ping: baidu.com: Name or service not known很明显&#xff0c;这是DNS解析问题。 于是我修改 /etc/resolv.conf 文件后&#xff0c;执行完sudo systemctl restart NetworkManager&#xff0c;/etc/resolv.con…

Linux-多线程2 ——线程等待、线程异常、线程退出、线程取消和线程分离

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 一、线程间的全局变量共享__thread 修饰全局变量 二、线程等待pthread_self和tid 三、线程异常四、线程退出五、线程取消六、线程分离 一、线程间的全局变量共享 上…

ttkbootstrap界面美化系列之主窗口(二)

一&#xff1a;创建主窗口 在利用ttkbootstrap构建应用程序时&#xff0c;可以用tkinter传统的tk方法来创建主界面&#xff0c;也可以用ttkbootstrap中的window类来创建&#xff0c;下面我们来看看两者的区别 1&#xff0c;传统方法创建主界面 import tkinter as tk import …

力扣思路题:最长特殊序列1

int findLUSlength(char * a, char * b){int alenstrlen(a),blenstrlen(b);if (strcmp(a,b)0)return -1;return alen>blen?alen:blen; }

[CVPR-24] Text-to-3D using Gaussian Splatting

3DGS对初始化敏感&#xff1b;引入基于Point-E的3D SDS可以缓解多脸问题&#xff1b;外观细化阶段可以有效抑制异常点&#xff0c;并提高可视化效果&#xff1b;不需要对SDS的改进&#xff0c;用gudiance scale100可以取得很不错的结果。 [pdf | proj | code] 方法 Geometry O…

Linux——动静态库的制作及使用与动态库原理

目录 一、静态库 1.静态库的制作 2.静态库的使用 加载静态库方法一&#xff1a;安装头文件与库文件 加载静态库方法二&#xff1a;指定文件目录 二、动态库 1.动态库的制作 2.动态库的使用 方法一&#xff1a;安装到系统中 方法二&#xff1a;软链接 方法三&…

c语言文件操作(中)

目录 1. 文件的顺序读写1.1 顺序读写函数1.2 顺序读写函数的原型和介绍 结语 1. 文件的顺序读写 1.1 顺序读写函数 函数名功能适用于fgetc字符输入函数所有输出流fputc字符输出函数所有输出流fgets文本行输入函数所有输出流fputs文本行输出函数所有输出流fscanf格式化输入函数…

刷题DAY24 | LeetCode 77-组合

1 回溯法理论基础 回溯法也可以叫做回溯搜索法&#xff0c;它是一种搜索的方式。回溯是递归的副产品&#xff0c;只要有递归就会有回溯。 所以以下讲解中&#xff0c;回溯函数也就是递归函数&#xff0c;指的都是一个函数。 1.1 回溯法的效率 回溯法的性能如何呢&#xff0…

完整指南:如何使用 Stable Diffusion API

Stable Diffusion 是一个先进的深度学习模型&#xff0c;用于创造和修改图像。这个模型能够基于文本描述来生成图像&#xff0c;让机器理解和实现用户的创意。使用这项技术的关键在于掌握其 API&#xff0c;通过编程来操控图像生成的过程。 在探索 Stable Diffusion API 的世界…

HarmonyOS NEXT应用开发之Web获取相机拍照图片案例

介绍 本示例介绍如何在HTML页面中拉起原生相机进行拍照&#xff0c;并获取返回的图片。 效果预览图 使用说明 点击HTML页面中的选择文件按钮&#xff0c;拉起原生相机进行拍照。完成拍照后&#xff0c;将图片在HTML的img标签中显示。 实现思路 添加Web组件&#xff0c;设置…

Vue.js+SpringBoot开发食品生产管理系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 加工厂管理模块2.2 客户管理模块2.3 食品管理模块2.4 生产销售订单管理模块2.5 系统管理模块2.6 其他管理模块 三、系统展示四、核心代码4.1 查询食品4.2 查询加工厂4.3 新增生产订单4.4 新增销售订单4.5 查询客户 五、…

生成式人工智能服务安全基本要求实务解析

本文尝试明晰《基本要求》的出台背景与实践定位&#xff0c;梳理《基本要求》所涉的各类安全要求&#xff0c;以便为相关企业遵循执行《基本要求》提供抓手。 引言 自2022年初以来&#xff0c;我国陆续发布算法推荐、深度合成与生成式人工智能服务相关的规范文件&#xff0c;…

阿里云服务器ECS经济型e实例性能如何?

阿里云服务器ECS推出经济型e系列&#xff0c;经济型e实例是阿里云面向个人开发者、学生、小微企业&#xff0c;在中小型网站建设、开发测试、轻量级应用等场景推出的全新入门级云服务器&#xff0c;CPU采用Intel Xeon Platinum架构处理器&#xff0c;支持1:1、1:2、1:4多种处理…

JS第一阶段1

文章目录 1. js组成2. JS三种书写位置JS输出语句 3. 变量4. 数据类型Number字符串型 String布尔型booleanUnddefined和Null 5. 获取变量的数据类型获取检测变量的数据类型 6. 数据转换类型转换为字符串转换为数字型&#xff08;重点&#xff09;转换为布尔型 7.运算符算数运算符…

找不到msvcp110.dll怎么办,msvcp110.dll丢失的5种修复方法

在计算机使用过程中&#xff0c;我们经常会遇到一些错误提示&#xff0c;其中之一就是“msvcp110.dll丢失”。由于msvcp110.dll是Microsoft Visual C Redistributable Package的重要组成部分&#xff0c;它的缺失会导致依赖于该组件的软件无法正常启动或运行&#xff0c;比如某…

从初学者到专家:Java反射的完整指南

一.反射的概念及定义 Java 的反射&#xff08; reflection &#xff09;机制是在运行状态中&#xff0c;对于任意一个类&#xff0c;都能够知道这个类的所有属性和方法&#xff1b;对于任意一个对象&#xff0c;都能够调用它的任意方法和属性&#xff0c;既然能拿到那么&#x…