KMP算法


前言

KMP算法新地址:数据结构:KMP(考研 + 竞赛),本文理解较浅,思路不清晰,可前往新地址去查看,为最新的理解。


一、KMP算法适用场景

KMP算法就是能快速找到一个模板串是否有子串的算法,这里我们直接用题目去分析,就本人目前而言,KMP算法感觉是最难理解的算法,如果不能理解,建议背下来模板先用,后续再慢慢理解


二、例题,模板

1.模板

本模板来自AcWing算法基础课

// s[]是长文本,p[]是模式串,n是s的长度,m是p的长度
//求模式串的Next数组:
for (int i = 2, j = 0; i <= m; i ++ )
{
    while (j && p[i] != p[j + 1]) j = ne[j];
    if (p[i] == p[j + 1]) j ++ ;
    ne[i] = j;
}

// 匹配
for (int i = 1, j = 0; i <= n; i ++ )
{
    while (j && s[i] != p[j + 1]) j = ne[j];
    if (s[i] == p[j + 1]) j ++ ;
    if (j == m)
    {
        j = ne[j];
        // 匹配成功后的逻辑
    }
}

2.AcWing 831. KMP字符串

本题链接:AcWing 831. KMP字符串
本博客给出本题截图:

在这里插入图片描述

本题分析

根据例子去讲解本算法,首先s数组存储的是模板串,p数组存储的是子串

接下来举一个栗子:
模板串(s):{aabaabaaf}
子 串 (p) :{aabaaf}
关于ne数组:ne[i]存储的是模式串中每个前缀最长的能匹配前缀子串的结尾字符的下标,同样也表示这一前缀和后缀最长相等的长度
KMP算法的实现:
s:aabaabaaf
p:aabaaf
012345678
我们通过观察可以知道在第5位的时候模板串和子串不匹配
按照最暴力的做法接下来我们应该把子串向后移动一个位置,即拿p[0]和s[1]进行比较,如果不能匹配,那么拿p[0]和s[2]比较,依次类推,这显然是很麻烦的
如果我们能预处理出来这么一个东西,即找到p[5]之前的元素的最长公共子序列:即对于{aabaa},找到最长公共子序列{aa},那么我们就可以进行如下操作:
直接把数组p平移为
aabaabaaf
   aabaaf
就可以省去很多的步骤,从而达到优化代码的结果,接下来我们来看如何实现这种操作
计算数组p的每一位的ne
ne[0] = 0,显然对于单独一个字母,不存在子序列
ne[1] = 1,a和a匹配
ne[2] = 0,{a,a,b}中不存在前缀 = 后缀
ne[3] = 1,{a,a,b,a}中,a和a匹配
ne[4] = 2,{a,a,b,a,a}中,aa和aa披人皮
ne[5] = 0,{a,a,b,a,a,f}中不存在前缀 = 后缀

所以本例子中的ne数组就是{0,1,0,1,2,0}
然后我们把这个数组中的元素直接右移1变成{-1,0,1,0,1,2}(不需要理解为什么这么去做)
当我们p[5]和s[5]不匹配的时候,我们采取的操作是直接继续让s[5]去和p[2]进行比较即我们直接让s[5]去和p[ne[5]]去比较,假令遍历p数组的指针为j,那么这一步的操作就是让j = ne[j];,所以让我们子串与模板串不匹配的时候,执行这个操作就可以实现快速匹配

接下来说一下怎么求ne数组

    for (int i = 2, j = 0; i <= n; i ++ )
    {
        while (j && p[i] != p[j + 1]) j = ne[j];
        if (p[j + 1] == p[i]) j ++;              
        ne[i] = j;
    }   

初始化ne[1] = 0;

AC代码

#include <iostream>

using namespace std;

const int N = 100010, M = 1000010;

char s[M], p[N];
int ne[N];

int main()
{
    int n, m;
    cin >> n >> p + 1 >> m >> s + 1;
    
    for (int i = 2, j = 0; i <= n; i ++ )
    {
        while (j && p[i] != p[j + 1]) j = ne[j];    //注意j不能为0
        if (p[j + 1] == p[i]) j ++;              
        ne[i] = j;
    }
    
    for (int i = 1, j = 0; i <= m; i ++ )
    {
        while (j && p[j + 1] != s[i]) j = ne[j];    //不匹配的话就执行操作,注意j不能为0,即返回到头的话就没必要继续执行j = ne[j]了,否则会死循环
        if (p[j + 1] == s[i]) j ++;					//匹配的话就让j指针往后移
        if (j == n)                                 //证明成功找到了子串
        {
            cout << i - n << ' ';
            j = ne[j];                              //继续遍历,因为满足条件的起始下标不止可能一个
        }
    }
    
    return 0;
}

三、时间复杂度

关于KMP算法的时间复杂度以及证明,后续会给出详细的说明以及证明过程,目前先鸽了。

  • 14
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

辰chen

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值