首页 > 编程语言 >【字符串匹配】BF与KMP算法

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

时间:2024-03-18 22:33:29浏览次数:29  
标签:BF 匹配 sub int next 算法 KMP 字符串

一、字符串匹配问题

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

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

  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]],否则就不变

标签:BF,匹配,sub,int,next,算法,KMP,字符串
From: https://blog.csdn.net/qq_61903414/article/details/136824011

相关文章

  • AcWing 1171. 距离 Tarjan算法离线求LCA
    题目输入样例1:22121001221输出样例1: 100100输入样例2:32121031151232输出样例2: 1025LCA算法:LCA(LeastCommonAncestors)最近公共祖先Tarjan求LCA是一种离线的算法,也就是说它一遍求出所有需要求的点的LCA,而不是需要求哪两个点再去求......
  • 代码随想录算法训练营第五十天| ● 123.买卖股票的最佳时机III ● 188.买卖股票的
    买卖股票的最佳时机III  题目链接:123.买卖股票的最佳时机III-力扣(LeetCode)思路:与买卖股票2的区别在于我可以买卖两次,那么dp数组的状态就从两种变成了种,即第一次持有,第一次卖出,第二次持有,第二次卖出,注意这四种状态是不会同时存在的,除此之外还有一种状态,那就是不操作。if(......
  • 基于实体抽取-SMC-语义向量的大模型能力评估通用算法(附代码)
    大模型相关目录大模型,包括部署微调prompt/Agent应用开发、知识库增强、数据库增强、知识图谱增强、自然语言处理、多模态等大模型应用开发内容从0起步,扬帆起航。大模型应用向开发路径及一点个人思考大模型应用开发实用开源项目汇总大模型问答项目问答性能评估方法大模型......
  • C语言随记————简单算法
    1.问题:如何在C语言中实现一个简单的线性查找算法? 答案:线性查找算法可以通过遍历数组的每个元素,逐一比较来查找目标值。以下是一个简单的实现示例:intlinearSearch(intarr[],intn,intx){for(inti=0;i<n;i++){if(arr[i]==x)re......
  • 刷题日记——干碎那个BFS!(含国科大机试2021)
    例题小引——迷宫问题问题描述:迷宫由n行m列的单元格组成(n,m都小于等于50),每个单元格要么是空地,要么是障碍物。现请你找到一条从起点到终点的最短路径长度。分析——(迷宫问题BFS解法)使用BFS算法,进行广度优先遍历,总体思路是访问一个结点,就把相邻的结点入队,然后下一个访......
  • ISIS接口MD5 算法认证实验简述
    默认情况下,ISIS接口认证通过在ISIS协议数据单元(PDU)中添加认证字段,例如:MD5算法,用于验证发送方的身份。ISIS接口认证防止未经授权的设备加入到网络中,并确保邻居之间的通信是可信的。它可以有效地防止路由欺骗和其他恶意攻击。MD5(MessageDigestAlgorithm5)是一种常用的信......
  • Python算法练习
    练习Python算法可以帮助我们提高解决问题的能力、优化代码效率,并深入理解Python语言的特性。以下是一些Python算法练习的建议和示例:排序算法:实现常见的排序算法,如冒泡排序、插入排序、选择排序、快速排序、归并排序等,并比较它们的性能。练习应用排序算法解决实际问题,如查......
  • 算法详解——Dijkstra算法
      Dijkstra算法的目的是寻找单起点最短路径,其策略是贪心加非负加权队列一、单起点最短路径问题  单起点最短路径问题:给定一个加权连通图中的特定起点,目标是找出从该起点到图中所有其他顶点的最短路径集合。需要明确的是,这里关心的不仅仅局限于寻找一条从起点出发到任......
  • 代码随想录算法训练营第23天|669. 修剪二叉搜索树|108.将有序数组转换为二叉搜索树|53
    代码随想录算法训练营第23天|669.修剪二叉搜索树|108.将有序数组转换为二叉搜索树|538.把二叉搜索树转换为累加树|总结篇669.修剪二叉搜索树这道题目比较难,比添加增加和删除节点难的多,建议先看视频理解。题目链接/文章讲解:https://programmercarl.com/0669.%E4%BF%A......
  • 代码随想录算法训练营第27天|39. 组合总和|40.组合总和II|131.分割回文串
    代码随想录算法训练营第27天|39.组合总和|40.组合总和II|131.分割回文串详细布置39.组合总和本题是集合里元素可以用无数次,那么和组合问题的差别其实仅在于startIndex上的控制题目链接/文章讲解:https://programmercarl.com/0039.%E7%BB%84%E5%90%88%E6%80%BB%E5%9......