KMP算法【C++实现】

这篇具有很好参考价值的文章主要介绍了KMP算法【C++实现】。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

BF算法

字符串匹配,我们一般思路是被对比的串作为主串,对主串的的每一个字符串作为子串的开头,与要匹配的字符串进行匹配,匹配不成功则子串开头+1,要匹配的字符串回溯到1,进行匹配,直到匹配成功或者主串全部遍历完成,这就是BF算法。

分析时间复杂度,假设主串长度为n,匹配字符串长度为m,当第一次就匹配上的时候,执行1次,复杂度为O(1),最后一次才匹配到并且每次匹配都是最后一位才判断出不符合,复杂度为O((n-m+1)*m),平均复杂度太高了,效率非常低。

基于此,我们找到了一个新的匹配模式,即KMP算法。

KMP算法概念

KMP算法的基本思路是主串不回溯,子串部分回溯,通过对主串进行至多一次遍历即可判定字符串是否有匹配的值。

现在我们假定给出一个主串arr:

a b a b d a b a b c

一个子串brr:

a b a b c

很明显在第一次匹配时,子串和主串的前四个字符匹配成功,即a b a b,到第五个字符c,和主串第五个d不匹配,按照朴素匹配算法,我们要将主串回溯到第二个位置b,子串回溯到第一个位置a,重新进行匹配,复杂度很高。

观察后我们发现,主串第三四个字符其实是和子串第一二个字符是重合的,那么我们如果能找出一种方法,让字符串在匹配的时候直接略过重合的部分【也就是子串1 2 位置和主串3 4 位置】向后比较,那么时间复杂度会大大降低。

一个简单的例子就是上面假定给出的主串和子串,按照我们预计的这种算法,第一次匹配时第五个字符不匹配,前四个是相同的,而三四个字符等于子串一二个字符,所以第二次匹配时子串跳到第三个字符,主串跳到第五个字符进行比较,比较失败,第三次匹配子串回到第一个位置,主串跳到第六个位置,五个字符全部相同,匹配完成。

这趟匹配中,使用BF算法下我们要匹配五次,比较5+1+3+1+1+5次,而KMP算法下我们仅需要匹配三次,比较5+1+5次,当子串足够长的时候,KMP算法的优越性就显而易见了。 

对KMP算法的解释

通俗的来说,KMP算法就是通过寻找子串中前后最大相同子串【也就是相同前后缀】来减少比较次数。

别急,我们举一个小子串的例子来帮助大家理解。

a b a b

它的最长相同前后缀就是a b。

理解了这个概念基本就可以掌握kmp算法了。回到我么们最上方蓝色字体的子串主串,在第二次匹配的时候我们是通过子串跳到3,主串跳到5来减少比较次数的,思考为什么可以这样做。

其实就是主串第三四个字符,a b,与子串中前两个字符是相同的,所以我们跳过了这个我们已知的这部分匹配。

一个简单的初中数学,arr[2]+arr[3] == brr[2]+brr[3] , brr[2]+brr[3] == brr[0]+brr[1],所以arr[2]+arr[3] == brr[0]+brr[1],对吧?那么我们向arr[1] arr[2] 和 brr[1] brr[2]之间添加一个长度为n的相同的无规则字符串,这依然不影响我们上边的结论,也就是主串最后匹配的两个字符等于子串的前两个字符,所以我们可以跳过中间的一串,并提前将子串前两个和主串后两个重合。

KMP算法【C++实现】

 KMP算法【C++实现】

KMP算法【C++实现】

 

 KMP算法【C++实现】

 

这就是匹配过程,相比BF算法,节省了两次匹配的时间,当长度变长,减少的匹配次数会更多

 

 

那么KMP算法到现在就很清晰了,就是找到了子串所有子集的最长前后缀嘛,也就是说KMP算法的本质就是寻找子串的最长前后缀。

比如如果一个子串除去了最后一个字符【也就是这个子串的最大真子集】的最长前后缀是2,且这个子串正好比较到最后一个才发现不匹配,我们就可以直接跳过子串中的前两个,从子串第三个开始和主串匹配失败的那个开始作比较,因为最大真子集子串倒数两个字符等于子串前两个字符,而最大真子集子串最后两个字符和主串对应位置的字符匹配成功了嘛【可以去看蓝色字体的主串子串帮助理解】

只有当最后一个字符匹配失败才能用这个方法未免应用面太狭隘了,进一步思考可以想到,当倒数第二个字符匹配失败的时候,我们可以去找去除最后两个字符的子串子集的最大前后缀,倒数第三个字符匹配失败就去找去除最后三个字符的子串子集的最大前后缀......直到第一个第一个字符匹配失败,就可以子串不动,主串+1去匹配。我们可以用一个next数组来储存每一个子串子集的最长前后缀。

next数组的意义和求法

实际上next数组中存储的数据就是这个子串中长度为下标+1的子串子集的最大前后缀长度。

老样子,我们上个例子来帮助理解 a b a b c

对于这个子串,长度为一的子串子集就是a,它只有一个字符,所以最长前后缀没有意义,next[0]=0;

长度为二的子串子集是a b,他最后一个字符和第一个字符不同,next[1]=0;

长度为三的子串子集是a b a,它最后一个字符和第一个字符相同,next[2]=1;

长度为四的子串子集是a b a b,它倒数两个字符和前两个字符相同,next[3]=2;

长度为五的子串子集是a b a b c,它前后没有字符串相同,next[4]=0;

所以该子串的next数组就是0 0 1 2 0

这个数组内容其实除了表示最长相同前后缀,还表示匹配失败时应该跳回到子串的第几个位置进行比较,比如当第五个字符匹配失败时,我们找到长度为四的子串子集的最大相同前后缀,也就是next[3],它=2,所以在下一次匹配,我们可以直接把子串跳过子串[0]和子串[1],从子串[2]开始比较【也就是第三个字符】。

对于next数组的求解,我们可以进一步优化一下,不用每次都把子串子集所有前后缀都比较一遍,实际上,观察上边的next数组我们可以发现next数组的值最多只比前一个数值多一,这也很好理解,向前一个字符串的后边增加一个字符,它只在最后添加的字符等于之前字符串第 最长相同前缀+1 个字符时,它的最长前后缀长度才会+1,否则直接归零,所以我们只要看前一个next的值,再判断一次,就可以求出该next值。

话不多说,上代码:求解next数组,返回数组地址

int* renext(string one)
{
    int* nextarr = new int[one.length()+2];
    nextarr[0] = 0;
    for (int i = 1; i < one.length(); i++)
    {
        if (nextarr[i - 1] == 0)//如果前一个next为0,则这个只可能是0或1
        {
            if (one[i] == one[0])
                nextarr[i] = 1;
            else
                nextarr[i] = 0;
        }
        else//不然就是上一个next的数值+1或0
        {
            if (one[i] == one[nextarr[i-1]])
                nextarr[i] = nextarr[i - 1] + 1;
            else
                nextarr[i] = 0;
        }
    }
    return nextarr;
}

KMP算法函数代码:

int KMPsf(string all, string one)
{
    int cs1 = 0;//匹配成功个数
    int* nextarr = renext(one);
    int x = 0;
    while (x < all.length() && cs1 != one.length())//当没有对主串all全部遍历并且没有成功匹配时
    {
        if (all[x] == one[cs1])
            cs1++;
        else
            cs1 = nextarr[cs1];
        x++;
    }
    if (cs1 == one.length())//cs1即是匹配成功的字符数量,当与子串长度一致时就匹配成功
        cout << "yes" << endl;
    else
        cout << "no" << endl;
    return 0;
}

完整代码:

#include<iostream>
#include<string>
using namespace std;
//KMP算法
int* renext(string one)
{
    int* nextarr = new int[one.length()+2];
    nextarr[0] = 0;
    for (int i = 1; i < one.length(); i++)
    {
        if (nextarr[i - 1] == 0)
        {
            if (one[i] == one[0])
                nextarr[i] = 1;
            else
                nextarr[i] = 0;
        }
        else
        {
            if (one[i] == one[nextarr[i-1]])
                nextarr[i] = nextarr[i - 1] + 1;
            else
                nextarr[i] = 0;
        }
    }
    return nextarr;
}
int KMPsf(string all, string one)
{
    int cs1 = 0;//匹配成功个数
    int* nextarr = renext(one);
    int x = 0;
    while (x < all.length() && cs1 != one.length())//当没有对主串all全部遍历并且没有成功匹配时
    {
        if (all[x] == one[cs1])
            cs1++;
        else
            cs1 = nextarr[cs1];
        x++;
    }
    if (cs1 == one.length())//cs1即是匹配成功的字符数量,当与子串长度一致时就匹配成功
        cout << "yes" << endl;
    else
        cout << "no" << endl;
    return 0;
}
int main()
{
    string all;
    string one;
    cin >> all >> one;
    KMPsf(all, one);
    return 0;
}

为了减少一个匹配算法的时间复杂度,我们居然还要再写第二个匹配算法算出一个数组,这不能不说有一种奇妙的幽默在里面。但分析一下,我们假设主串长度为n,子串长度为m,求mext数组的时间复杂度仅仅只有O(m),因为后边匹配中主串不回溯,最多匹配n次,我们就按最坏匹配n次算,KMP算法的时间复杂度仅仅是O(m+n),可以看出时间复杂度降低的幅度是非常大的,而空间上仅仅增加了一个next数组,这实在是太实惠了。文章来源地址https://www.toymoban.com/news/detail-426671.html

到了这里,关于KMP算法【C++实现】的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • C#,字符串匹配(模式搜索)KMP算法的源代码与数据可视化

      D.E.Knuth   J.H.Morris KMP 算法(Knuth-Morris-Pratt 算法)是其中一个著名的、传统的字符串匹配算法,效率比较高。 KMP算法由 D.E.Knuth , J.H.Morris 和 V.R.Pratt 在 Brute-Force 算法的基础上提出的模式匹配的改进算法。因此人们称它为“克努特—莫里斯—普拉特算法”,简称KMP算法。K

    2024年01月25日
    浏览(41)
  • P3375 【模板】KMP 字符串匹配

    给出两个字符串 s 1 s_1 s 1 ​ 和 s 2 s_2 s 2 ​ ,若 s 1 s_1 s 1 ​ 的区间 [ l , r ] [l, r] [ l , r ] 子串与 s 2 s_2 s 2 ​ 完全相同,则称 s 2 s_2 s 2 ​ 在 s 1 s_1 s 1 ​ 中出现了,其出现位置为 l l l 。 现在请你求出 s 2 s_2 s 2 ​ 在 s 1 s_1 s 1 ​ 中所有出现的位置。 定义一个字符串 s s s 的

    2024年02月14日
    浏览(39)
  • PTA 7-1 字符串模式匹配(KMP)

    给定一个字符串 text 和一个模式串 pattern,求 pattern 在text 中的出现次数。text 和 pattern 中的字符均为英语大写字母或小写字母。text中不同位置出现的pattern 可重叠。 输入格式: 输入共两行,分别是字符串text 和模式串pattern。 输出格式: 输出一个整数,表示 pattern 在 text 中的出

    2024年02月06日
    浏览(30)
  • 【leetcode 力扣刷题】字符串匹配之经典的KMP!!!

    以下是能用KMP求解的算法题,KMP是用于字符串匹配的经典算法【至今没学懂………啊啊啊】 题目链接:28. 找出字符串中第一个匹配项的下标 题目内容: 题意还是很好理解的,要在字符串haystack中查找一个完整的needle,即字符串匹配。 暴力求解就是用 两层循环 :haystack从第

    2024年02月09日
    浏览(32)
  • Leetcode的AC指南 —— 字符串/KMP:28.找出字符串中第一个匹配项的下标

    摘要: Leetcode的AC指南 —— 字符串/KMP:28.找出字符串中第一个匹配项的下标 。题目介绍:给你两个字符串 haystack 和 needle ,请你在 haystack 字符串中找出 needle 字符串的第一个匹配项的下标(下标从 0 开始)。如果 needle 不是 haystack 的一部分,则返回 -1 。 题目介绍 :给你两

    2024年02月02日
    浏览(48)
  • 【动态规划】【字符串】C++算法:正则表达式匹配

    视频算法专题 动态规划汇总 字符串 给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 ‘.’ 和 ‘ ’ 的正则表达式匹配。 ‘.’ 匹配任意单个字符 \\\' ’ 匹配零个或多个前面的那一个元素 所谓匹配,是要涵盖 整个 字符串 s的,而不是部分字符串。 示例 1: 输入:

    2024年02月03日
    浏览(44)
  • 使用Java实现高效的字符串匹配算法

    摘要:字符串匹配是计算机领域中的一个重要问题,有着广泛的应用场景。在本篇博客文章中,我们将介绍几种高效的字符串匹配算法,并给出使用Java语言实现的代码示例,希望能对读者理解和应用这些算法有所帮助。 一、KMP算法 KMP算法(Knuth-Morris-Pratt算法)是一种经典的

    2024年02月16日
    浏览(28)
  • 数据结构--字符串的KMP算法

    朴素模式匹配算法: 一旦发现当前这个子串中某个字符不匹配,就只能转而匹配下一个子串(从头开始) 但我们可以知道: 不匹配的字符之前,一定是和模式串一致的 color{red}不匹配的字符之前,一定是和模式串一致的 不匹配的字符之前,一定是和模式串一致的 我们可以利用

    2024年02月12日
    浏览(50)
  • LeetCode:459. 重复的子字符串 —【2、KMP算法】

    🍎道阻且长,行则将至。🍓 🌻算法,不如说它是一种思考方式🍀 算法专栏: 👉🏻123 题目描述 :给定一个非空的字符串 s ,检查是否可以通过由它的一个子串重复多次构成。 来源:力扣(LeetCode) 难度: 简单 提示: 1 = s.length = 104 s 由小写英文字母组成 示例 1: 输入:

    2024年02月04日
    浏览(45)
  • 代码随想录 Leetcode459. 重复的子字符串(KMP算法)

            此解法读者需要了解什么是KMP算法以及KMP算法中next数组的具体含义才能理解         因为在KMP算法的next数组中,next[index]表示 i ndex之前的最大长度的相同前缀后缀值 ,那么要判断整个字符串中是否由重复字串构成,只需要以下两个条件:         1.next[n - 1] !=

    2024年01月19日
    浏览(54)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包