kmp算法(小白整理)

kmp算法   分类:字符串  

/*

这里推荐一位B站up主的视频,我的思路也借鉴于他。传送门:

理论篇:

帮你把KMP算法学个通透!(理论篇)_哔哩哔哩_bilibili

代码实现篇:

帮你把KMP算法学个通透!(求next数组代码篇)_哔哩哔哩_bilibili

*/

目录

初遇kmp

1.正常思路:暴力枚举

2.kmp算法的大致思路

3.kmp算法的next数组引入

4.kmp算法的具体实现


初遇kmp

kmp算法 通常的用途是:给你一段长字符串(母串),再给你一段短字符串(子串),让你找长字符串中有多少个短字符串出现。/或者让你在长字符串中,找满足条件的子串。

比如现在有一个母串和一个子串,让你找母串中子串第一次出现的位置。

文本串(母串)s1    aabaabaaf  

模式串(子串)s2    aabaaf       

为了从其他文章过来的同学更好的计算时间复杂度,这里用 n表示s1的长度,m表示s2的长度。

1.正常思路:暴力枚举

两层for循环,忽略时间复杂度O(n*m),肯定能走过每一种情况。

for(int i=0;i<s1.size();i++) //这里s1是string类型,如果是char数组的话,用strlen(s1)表示长度一样的。

{

     for(int j=0;j<s2.size();j++)

     {

         if(s1[i+j]!=s2[j]) //j是比较时s2字符串的下标,i+j是s1比较到的下标位置

             break;

     }

}

2.kmp算法的大致思路

如果暴力枚举的话,字符串长度很短的情况,是可以卡过去的,但是当长字符串到了1e6~1e7的长度左右,很容易被卡时间。

所以我们考虑,有没有什么办法来优化呢?我们可以发现,在比较两字符串是否相等的过程中,有很多重复无意义的比较。

比如上文给出的字符串s1和s2,我比较到i=0,j=6的字符串,能发现s1中的 aabaab前5位 与s2的 aabaaf前5位 相同,但是第6位 b!=f,所以i++,j=0 再次从头比较。

但是聪明的你们肯定也发现了,我们第一次比较,虽然没有找出子串,但是在s1中找出了两个aab,都与s2的前三位aab相等。

如果我们想找完全相等的字符串的话,第一个aab已经被否定,那我们为什么不直接从第二个aab再往后找呢?

这就是kmp算法相比正常暴力枚举所优化的地方。

3.kmp算法的next数组引入

为了实现刚才那种人能想到,但计算机想不到的思路,我们需要先对子串进行处理

大多数文章中用 next数组/pre数组 来表示子串的前缀表,这里我怕大家混淆其它算法中的next含义,我用kmp数组来表示

真前后缀概念:不用概念,一看就懂。注意一下真前后缀不包含自身。

即aabaaf的前缀子串有:a,aa,aab,aaba,aabaa   后缀子串有:f,af,aaf,baaf,abaaf

从i=0到i=5比较,求每个前缀子串的最长相等前后缀的长度

               简单说明:比如aabaa串,最长的一段,是它的前缀aa和后缀aa相等,长度为2

a0无前缀 无后缀    长度为0
aa1前缀a 后缀a    长度为1   a
aab0前缀aa 后缀ab    长度为0
aaba1前缀aab,后缀aba    长度为1a
aabaa2前缀aaba 后缀abaa   长度为2aa
aabaaf0前缀aabaa 后缀abaaf   长度为0

可得:前缀表(临时数组) 010120  //kmp数组

//伪代码如下:

int kmp[1000010]; //全局变量初值为0,如果有多组测试样例,记得每次初始化

void getkmp(子串s)

{

    memset(kmp,0,sizeof(kmp));

    初始化  j=0;  

    kmp[0]=0;

    for(i=1; i<s.size(); i++)   //i继续匹配,for循环捋一遍子串

    {

        前后缀不相同

        while(s[i]!=s[j] && j>0) //if会错,需要连续回退,0是边界

             j=kmp[j-1];

        前后缀相同

        if(s[i]==s[j])

            kmp[i]=++j;  //j继续匹配,并且kmp[i]赋值

    }

}

4.kmp算法的具体实现

学会如何得到kmp数组之后,那找到了前缀表有什么用呢?

kmp数组,我们能得到子串的最长相等前后缀,我们能根据它相等的部分,直接从相等部分的下一位开始比较。

比如 母串aabaabaaf,子串aabaaf

我们暴力枚举,当i=0,j=6比较到aabaab与aabaaf并不相等时,我们不用再让i=1,j=0重新比较。

我们重新定义i和j,让i指向母串s1的下标,j指向子串s2的下标。

头脑风暴,前方高能


我们这样移动i和j,i和j同时移动并对比,i=5并且j=5时,因为b!=f,所以我们将j回退,考虑我们如果能比较到f这个位置,那b前面(母串)一定存在aa和aabaaf(子串)中f前的aa重复,而aab!=aaf,则我们回退到上一个aa(子串)的位置。

因为之前计算的kmp数组,当子串为aabaa时,最长相等前后缀为2,即当下标为2时,前边的aa相当于比完了,所以j回退到2,i仍然是5,s2[j]与s1[i]再进行比较,我们就能直接比较出aab(母串)与aabaaf(子串)前面相等,于是i++,j++,继续向后比较,直到在母串中找出子串。

考虑一种情况,如果母串是aac,子串aabaaf,我们j回退到2,s1[i]=’c’ , s2[j]=’b’,还是不相等怎么办?那就和之前一样处理,找c前面的子串对应的前缀表,可以看到aa对应值为1,所以j回退到1,再次进行比较。如果j一直回退到0,还没有找到相等的部分,那就说明已经退到边界了,得重新比较了。


如果光看分析看不懂的话,建议结合代码,自己拿张纸画出子串和母串,然后手动模拟观察i和j的位置。

//代码实现部分:

    for(int i=1,j=0;i<s2.size();i++) //得到kmp数组,s1长字符串(母串),s2短串(子串)
    {
        while(j && s2[i]!=s2[j])
        {
            j=kmp[j-1];
        }

        if(s2[i]==s2[j])
        {
            kmp[i]=++j;
        }
    }
    for(int i=0,j=0;i<s1.size();i++)     //在s1串中找到第一个s2串
    {
        while(j && s1[i]!=s2[j])
        {
            j=kmp[j-1];
        }

        if(s1[i]==s2[j])
        {
            j++;
        }

        if(j==s2.size())
        {
            ans=i;
            break;
        }
    }

板子题:P3375 【模板】KMP字符串匹配 -> kmp模板题(洛谷)

AC代码如下:

/*
P3375 【模板】KMP字符串匹配
https://www.luogu.com.cn/problem/P3375
思路:此题要求不止找到一个出现的子串,而是要找到所有出现过的子串,所以我们需要根据题意,修改查找部分的代码。
*/
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
typedef long long ll;
string s1,s2;
int kmp[N];
int main()
{
    cin>>s1>>s2;
    //printf("%d %d\n",s1.size(),s2.size());
    for(int i=1,j=0;i<s2.size();i++)
    {
        while(j && s2[i]!=s2[j])
        {
            j=kmp[j-1];
        }
        if(s2[i]==s2[j])
        {
            kmp[i]=++j;
        }
    }
    for(int i=0,j=0;i<s1.size();i++)
    {
        while(j && s1[i]!=s2[j])
        {
            j=kmp[j-1];
        }
        if(s1[i]==s2[j])
        {
            j++;
        }
        //if(i-j==-1)
         //       printf("%d %d\n",i,j);
        if(j==s2.size())
        {

            printf("%d\n",i-j+2);
            j=kmp[j-1];
        }
    }
    for(int i=0;i<s2.size();i++)
        printf("%d ",kmp[i]);
    return 0;
}

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
KMP算法是一种字符串匹配算法,用于在一个字符串中查找另一个较短的模式串的出现位置。它的实现基于两个关键概念:部分匹配表和最大匹配前缀后缀数组(next数组)。通过构建next数组,KMP算法能够在失配时将模式串跳过一定长度,从而提高了匹配的效率。 在给定的引用内容中,有几个代码片段涉及到了使用Python实现KMP算法。这些代码分别是通过暴力搜索、利用next数组进行匹配以及计算next数组的实现。暴力搜索算法的时间复杂度为O(m*n),而KMP算法的时间复杂度为O(m+n),其中m为主串的长度,n为模式串的长度。 在KMP算法中,next数组记录了模式串中每个位置之前的最长公共前缀和最长公共后缀的长度。通过根据next数组的值来调整模式串的位置,KMP算法能够避免不必要的比较,从而提高匹配效率。 因此,Python中的KMP算法可以通过构建next数组来实现,在匹配过程中根据失配时的next值进行模式串的调整,直到找到匹配位置或匹配失败。这样可以在较短的时间内找到模式串在主串中的位置。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [KMP算法(python)](https://download.csdn.net/download/weixin_38618540/14854290)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [KMP算法(Python)](https://blog.csdn.net/m0_52238102/article/details/115830347)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

哑笙

今天星期四,v我50听重生故事

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值