KMP算法

前言

哎呀!我们来刷题吧!

来看看这一道题:

有两个字符串 S S S T T T
T T T出现在 S S S中的第一个位置
1 ≤ ∣ S ∣ ≤ 1 0 6 1\le|S|\le10^6 1S106 1 ≤ ∣ T ∣ ≤ 1 0 6 1\le|T|\le10^6 1T106

我和我的小伙伴们都惊呆了,这没法用 O ( ∣ S ∣ × ∣ T ∣ ) O(|S|\times|T|) O(S×T)做啊,那怎么办呢?

我们就可以用KMP算法了!

KMP

啥是KMP

在计算机科学中,Knuth-Morris-Pratt 字符串查找算法(简称:KMP 算法)可在一个 文本串 T T T 内查找一个 模式串 P P P 的出现位置。该算法通过对这个词在不匹配时本身包含的信息来确定下一个匹配将在哪里开始的发现,从而避免重新检查先前匹配的字符,提高程序运行效率。

具体来说,KMP 算法在匹配前会预处理模式串 P P P,得到一个 f a i l fail fail 数组。借助 failfailfail 数组,可以在匹配过程中减少很多冗余的匹配操作,由此提高了算法的效率。KMP 算法的时间复杂度为 O ( n + m ) \mathcal{O}(n+m) O(n+m),其中 n n n m m m 分别表示两个串的长度。

关于fail数组

KMP 算法的核心是 f a i l fail fail 数组 对于字符串 s = s 0 s 1 … s n − 1 s=s_0 s_1\ldots s_{n-1} s=s0s1sn1 ,如果 jjj 是满足 s 0 … j = s i − j … i s_{0\ldots j}=s_{i-j \ldots i} s0j=siji 的最大值,则 f a i l i = j fail_i=j faili=j(注意 j < i j< i j<i),其中 s a … b s_{a \ldots b} sab 表示字符串 s s s 从下标 a a a 到下标 b b b 的子串,即 s a s a + 1 … s b − 1 s b s_as_{a+1}\ldots s_{b-1}s_b sasa+1sb1sb 对于不存在 s 0 … j = s i − j … i s_{0\ldots j}=s_{i-j \ldots i} s0j=siji的情况, f a i l i = − 1 fail_i=-1 faili=1

例如,对于字符串aababaab,则 f a i l fail fail 值如下表所示:

aababaab
f a i l fail fail − 1 -1 1 0 0 0 − 1 -1 1 0 0 0 − 1 -1 1 0 0 0 1 1 1 2 2 2

匹配过程

理解了 f a i l fail fail,我们先不急着研究怎么计算 f a i l fail fail,而是先看看如何借助 f a i l fail fail值快速地进行字符串匹配

我们假设 f a i l fail fail数组已经正确计算出来了。
在这里插入图片描述对于上图比较过程,当’a’和’?‘和比较的时候比较失败了,那么我们需要把母串的起始位置移动到下一个位置。对于 2 2 2 指向的匹配,这时候我们没有必要去从头开始一个一个比较,我们已经知道红色这一整块中间必然会有地方失配(失配就是匹配失败),为什么呢? 因为我们知道 f a i l [ i ] = 1 fail[i] = 1 fail[i]=1,如果红色的块能完美匹配,那么 f a i l [ i ] fail[i] fail[i] 应该等于 4 4 4,这和已知的 f a i l [ i ] = 1 fail[i]=1 fail[i]=1 矛盾。第 3 3 3, 4 4 4 次匹配过程也是同样的道理,没必要比较了,根据已知信息可以推导出来一定不可能匹配上了。所以我们直接跳到 f a i l [ i ] + 1 fail[i] + 1 fail[i]+1的位置进行匹配,也就是直接跳到匹配 5 5 5,因为’?'前面的串我们知道一定能和 f a i l [ i ] + 1 fail[i] + 1 fail[i]+1前面的串匹配。 这就是 KMP 匹配的本质原理,利用已经计算过的信息加速匹配过程。

板子

#include <iostream>
#include <cstring>
using namespace std;
const int maxn = 100;
int fail[maxn];
void getFail(char *P){
    int m=strlen(P);
    fail[0]=-1;
    for(int i=1;i<m;i++)
    {
        int j=fail[i-1];
        while(j>=0 && P[j+1]!=P[i]){
            j=fail[j];
        }
        if(P[j+1]==P[i]){
            j++;
        }
        fail[i]=j;
    }
}
int KMP(char *T,char *P){
    int n=strlen(T),m=strlen(P);
    int j=-1;
    for(int i=0;i<n;i++){
        while(j>=0 && P[j+1]!=T[i]){
            j=fail[j];
        }
        if(P[j+1]==T[i]){
            j++;
            if(j+1==m){
                return i-m+1;
            }
        }
    }
    return -1;
}
int main() {
    char s[maxn],t[maxn];
    cin>>s>>t;
    getFail(t);
    cout<<KMP(s,t)<<endl;
    return 0;
}

解释代码

int KMP(char *T,char *P){
    int n=strlen(T),m=strlen(P);
    int j=-1;
    for(int i=0;i<n;i++){
        while(j>=0 && P[j+1]!=T[i]){
            j=fail[j];
        }
        if(P[j+1]==T[i]){
            j++;
            if(j+1==m){
                return i-m+1;
            }
        }
    }
    return -1;
}

KMP板子

void getFail(char *P){
    int m=strlen(P);
    fail[0]=-1;
    for(int i=1;i<m;i++)
    {
        int j=fail[i-1];
        while(j>=0 && P[j+1]!=P[i]){
            j=fail[j];
        }
        if(P[j+1]==P[i]){
            j++;
        }
        fail[i]=j;
    }
}

f a i l fail fail数组

结语

好了,今天的KMP算法已经讲完了,如果有问题大家可以提出来!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值