首页 > 编程语言 >KMP算法

KMP算法

时间:2024-12-21 14:20:27浏览次数:4  
标签:nxt typedef string int 算法 KMP border define

更新日志 2024/12/21:开工。

作用

KMP算法本质作用是求字符串前缀的最长 border。

border:同时是一个字符串前缀和后缀的字符串,称为前者的 border。

常见的,我们可以使用它进行字符串匹配。

思路

假如我们要在 \(s_1\) 中匹配 \(s_2\)。

我们使用 nxt 数组储存 \(s_2\) 所有前缀的 border。

假如当前这一位两个字符串失配,也就是字符匹配不上,我们就需要找到下一个 \(s_2\) 前缀来匹配。

如果是暴力算法,那么我们每次都会重新从 \(s_2\) 的开头匹配,但 KMP 可以找到更近的可行位置。

假如 \(s_2\) 的第 \(j\) 位失配了,那么 \([1,j-1]\) 必然是匹配上的,那么我们需要找到的最近的可行位置,它前面的部分必然也是匹配上了的。

形式化的说,假如 \(i\) 的前一个可行位置为 \(j\),则 \(s[1,j-1]=s[i-j+1,i-1]\)。

将那么, \(s[1,j-1]\) 就是 \(s[1,i-1]\) 的一个 border。

故而我们只需要找出每个前缀的最大 border,就可以求出下一个匹配位置了。

模板

前言

KMP算法有两种常见实现方式。这里的实现方式并不是指代码写法,而是指 nxt 数组内涵

它们分别有各自特定的用处与优势。

事实上二者是通用的,只不过在各自方面代码稍微简单一些而已。

下面默认字符串从 \(1\) 开始编号。

border重心

这个写法中,nxt 数组储存了 \([1,i]\) 的 border 长度。

这样可以很方便地处理后续与 border 有关的操作,但是求 nxt 与匹配时会略微复杂。

一般情况下,除非题目要求 border,还是用匹配重心更方便些。

求nxt

int nxt[N];
void getnxt(string &s){
    int i=1,j=0;
    nxt[0]=-1;
    while(i<s.size()){
        if(j&&s[i]!=s[j])j=nxt[j-1]+1;
        else nxt[i++]=j++;
    }
}

单次查找

如果当前第 \(|s_2|\) 位也已完成匹配,整个 \(s_2\) 就匹配完成了。说明是子串

bool kmp(string &s1,string &s2,int nxt[]){
    int i=1,j=1;
    while(i<s1.size()){
        if(j&&s1[i]!=s2[j])j=nxt[j-1]+1;
        else i++,j++;
        if(j==s2.size())return 1;
    }
    return 0;
}

全部位置

当前匹配完后,此时 \(j\) 指向的是空字符,下一步必然失配,所以可以直接这么写。

void kmp(string &s1,string &s2,int nxt[],vec<int> &ans){
    int i=1,j=1;
    while(i<s1.size()){
        if(j&&s1[i]!=s2[j])j=nxt[j-1]+1;
        else i++,j++;
        if(j==s2.size())ans.pub(i-j+1);
    }
}

匹配重心

在这种写法中,nxt 储存 \([1,i-1]\) 的 border,这样求 nxt 和匹配都会变得更简洁。

但如果有 border 有关操作就会变得复杂。

求nxt

int nxt[N];
void getnxt(string &s){
    int i=1,j=0;
    while(i<s.size()){
        if(j&&s[i]!=s[j])j=nxt[j];
        else nxt[++i]=++j;
    }
}

单次查找

bool kmp(string &s1,string &s2,int nxt[]){
    int i=1,j=1;
    while(i<s1.size()){
        if(j&&s1[i]!=s2[j])j=nxt[j];
        else i++,j++;
        if(j==s2.size())return 1;
    }
    return 0;
}

全部位置

原理与border重心相同。

void kmp(string &s1,string &s2,int nxt[],vec<int> &ans){
    int i=1,j=1;
    while(i<s1.size()){
        if(j&&s1[i]!=s2[j])j=nxt[j];
        else i++,j++;
        if(j==s2.size())ans.pub(i-j+1);
    }
}

优化

不推荐使用。

这个优化在单次匹配时有效,但会导致求border和全部位置时失效,并且KMP本来就是 \(O(n)\) 算法,所以不推荐使用。

原理是如果下一个位置的字符与当前字符相同那显然不用匹配,跳过这一步即可。

以匹配重心为例。

int nxt[N];
void getnxt(string &s){
    int i=1,j=0;
    while(i<s.size()){
        if(j&&s[i]!=s[j])j=nxt[j];
        else{
            if(s[++i]==s[++j])nxt[i]=nxt[j];
            else nxt[i]=j;
        }
    }
}

例题

字符串匹配

LG3375

题目中要求border,所以使用border重心会方便一些。

代码
#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef __int128 i128;
typedef double db;
typedef long double ld;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
typedef pair<int,ll> pil;
typedef pair<ll,int> pli;
template <typename Type>
using vec=vector<Type>;
template <typename Type>
using grheap=priority_queue<Type>;
template <typename Type>
using lrheap=priority_queue<Type,vector<Type>,greater<Type> >;
#define fir first
#define sec second
#define pub push_back
#define pob pop_back
#define puf push_front
#define pof pop_front
#define chmax(a,b) a=max(a,b)
#define chmin(a,b) a=min(a,b)
#define rep(i,x,y) for(int i=x;i<=y;i++)
#define per(i,x,y) for(int i=x;i>=y;i--)

const int inf=0x3f3f3f3f;
const ll INF=0x3f3f3f3f3f3f3f3f;
const int mod=998244353;

const int N=1e6+5;

string s1,s2;
int nxt[N];
void getnxt(string &s){
    int i=1,j=0;
    nxt[0]=-1;
    while(i<s.size()){
        if(j&&s[i]!=s[j])j=nxt[j-1]+1;
        else nxt[i++]=j++;
    }
}
void kmp(string &s1,string &s2,int nxt[],vec<int> &ans){
    int i=1,j=1;
    while(i<s1.size()){
        if(j&&s1[i]!=s2[j])j=nxt[j-1]+1;
        else i++,j++;
        if(j==s2.size())ans.pub(i-j+1);
    }
}

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    cin>>s1>>s2;
    s1=' '+s1;s2=' '+s2;
    getnxt(s2);
    vec<int> ans;
    kmp(s1,s2,nxt,ans);
    for(auto it:ans)cout<<it<<"\n";
    rep(i,1,s2.size()-1)cout<<nxt[i]<<" ";
    return 0;
}

当然也有匹配重心的写法。

代码
#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef __int128 i128;
typedef double db;
typedef long double ld;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
typedef pair<int,ll> pil;
typedef pair<ll,int> pli;
template <typename Type>
using vec=vector<Type>;
template <typename Type>
using grheap=priority_queue<Type>;
template <typename Type>
using lrheap=priority_queue<Type,vector<Type>,greater<Type> >;
#define fir first
#define sec second
#define pub push_back
#define pob pop_back
#define puf push_front
#define pof pop_front
#define chmax(a,b) a=max(a,b)
#define chmin(a,b) a=min(a,b)
#define rep(i,x,y) for(int i=x;i<=y;i++)
#define per(i,x,y) for(int i=x;i>=y;i--)

const int inf=0x3f3f3f3f;
const ll INF=0x3f3f3f3f3f3f3f3f;
const int mod=998244353;

const int N=1e6+5;

string s1,s2;
int nxt[N];
void getnxt(string &s){
    int i=1,j=0;
    while(i<s.size()){
        if(j&&s[i]!=s[j])j=nxt[j];
        else nxt[++i]=++j;
    }
}
void kmp(string &s1,string &s2,int nxt[],vec<int> &ans){
    int i=1,j=1;
    while(i<s1.size()){
        if(j&&s1[i]!=s2[j])j=nxt[j];
        else i++,j++;
        if(j==s2.size())ans.pub(i-j+1);
    }
}

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    cin>>s1>>s2;
    s1=' '+s1;s2=' '+s2;
    getnxt(s2);
    vec<int> ans;
    kmp(s1,s2,nxt,ans);
    for(auto it:ans)cout<<it<<"\n";
    rep(i,1,s2.size()-1)cout<<nxt[i+1]-1<<" ";
    return 0;
}

nxt运用

有时候我们也会专门用到 nxt 数组。

CF432D

简要题解

另一种码风

全部以单次匹配为例吧,找出全部位置略加修改即可。

border重心

int nxt[N];
void getnxt(string &s){
    nxt[0]=-1;
    for(int i=1,j=0;i<s.size();i++,j++){
        while(j&&s[i]!=s[j])j=nxt[j-1]+1;
        nxt[i]=j;
    }
}
bool kmp(string &s1,string &s2,int nxt[]){
    for(int i=1,j=1;i<s1.size();i++,j++){
        while(j&&s1[i]!=s2[j])j=nxt[j-1]+1;
        if(j==s2.size()-1)return 1;
    }
    return 0;
}

匹配重心

int nxt[N];
void getnxt(string &s){
    for(int i=1,j=0;i<s.size();){
        while(j&&s[i]!=s[j])j=nxt[j];
        nxt[++i]=++j;
    }
}
bool kmp(string &s1,string &s2,int nxt[]){
    for(int i=1,j=1;i<s1.size();i++,j++){
        while(j&&s1[i]!=s2[j])j=nxt[j];
        if(j==s2.size()-1)return 1;
    }
    return 0;
}

标签:nxt,typedef,string,int,算法,KMP,border,define
From: https://www.cnblogs.com/HarlemBlog/p/18620723

相关文章

  • 计算机视觉:YOLO V5目标检测算法模型
    1.YOLOV5模型概述1.1YOLOv5的概念YOLOv5是一种基于深度学习的目标检测模型,相较于YOLOv4,YOLOv5模型在目标检测精度和速度上都有了显著的提升。YOLOv5模型基于PyTorch开发,利用主干网络、检测头和损失函数等模块,能够实现对图像中多个目标的快速检测和定位。1.2YOLOv5模型......
  • 强化学习算法中的log_det_jacobian
    相关:https://colab.research.google.com/github/google/brax/blob/main/notebooks/training_torch.ipynb之前写过一篇同主题的文章,后来发现这个文章中有一些问题,不过也有些不好改动,于是就新开一篇来进行更正和补充!!!之前版本:https://www.cnblogs.com/xyz/p/18564777之所以之......
  • 1v1视频软件源码,如何优化快速排序算法低效问题?
    1v1视频软件源码,如何优化快速排序算法低效问题?快速排序快速排序也遵循分治的思想,它与归并排序不同的是,快速排序是原地排序,而且快速排序会先排序当前数组,再对子数组进行排序,它的算法步骤如下:1、哨兵划分:选取数组中最左端元素为基准数,将小于基准数的元素放在基准数左边,将......
  • 77.《排序算法实现》
    插入排序:直接插入排序:voidInsertSort(ElemTypeA[],intn){inti,j;for(inti=2;i<=n;i++)//从第二个元素开始遍历数组if(A[i]<A[i-1])//如果当前元素小于前一个元素{A[0]=A[i];//将当前元素暂存到A[0]......
  • 声音提取引擎算法
    声音提取引擎算法是一种用于从音频信号中提取有用信息的技术,广泛应用于语音识别、音频分析和声音处理等领域。我们可以总结出几种主要的声音提取算法及其应用。MFCC是最常用的语音特征提取方法之一,它通过傅里叶变换和滤波器组处理来捕捉语音信号的频率和振幅特征。MFCC的计算......
  • Luogu P8112 [Cnoi2021] 符文破译 题解 [ 蓝 ] [ KMP ] [ 线性 dp ] [ 决策单调性 dp
    符文破译:KMP+dp的好题。暴力dp不难打出一个暴力dp:设计\(dp_i\)表示当前前\(i\)位全部完成了匹配,所需的最小分割数。转移也是简单的,我们在KMP的过程中进行dp转移,每次选取next不断跳向再前面的next,然后进行转移即可。很显然一个字符集大小为\(1\)的串就能轻松......
  • 【算法】【优选算法】模拟
    目录一、模拟简介二、1576.替换所有的问号三、495.提莫攻击四、6.N字形变换五、38.外观数列六、1419.数⻘蛙一、模拟简介模拟就是依葫芦画瓢,题目会将如何做给出来,直接做出来就行。做题过程:先模拟算法流程,再将流程转化为代码。二、1576.替换所有的问号题目链接:1......
  • 用C#实现感知器算法——从零开始打造一个简单的机器学习模型!
    感知器(Perceptron)是一个经典的机器学习算法,常用于二分类问题。它是神经网络的基础,最早由FrankRosenblatt在1958年提出。今天,我们将用C#实现一个简单的感知器算法,让你理解感知器的工作原理,并能够亲自编码一个可用的模型。一、感知器算法概述感知器是一种线性分类器,其核心思想是......
  • 最优雅的算法——快速排序
    快速排序:探索两种流行的方法快速排序,这个听起来有点技术性的术语,实际上是一个既高效又优雅的算法,它能够将一堆混乱的数据快速整理得井井有条。今天,我们将通过一种轻松愉快的方式,一起揭开快速排序的神秘面纱,并探索两种流行的实现方法。话不多说,开始战斗快速排序的魔法:基......
  • 【字符串匹配算法——BF算法】
    ......