初探KMP算法
算法简述
KMP是单模匹配算法,在一个长度为n的文本串中查找一个长度为m的模式串。它的时间复杂度是O(m+n),是这类算法的理论最优复杂度。
模式匹配:在长为n的字符串S中,找到某个长为m的模式串P,复杂度至少是O(m+n)。
朴素的模式匹配算法
普通的暴力方法是从S中逐个字符开始匹配P中每个字符。最好复杂度是O(m+n),例如S = "abcdefg123", P = "123"
,只需处理7+3=10次。最差复杂度是O(mn),例如S = "aaaaaaab" ,P = "aab"
,要匹配5*3+3=18次。
KMP算法
KMP是在任何时候都能达到O(m+n)复杂度的算法。它通过预处理P,使得在与S匹配时能够跳过一些字符,达到快速匹配的目的。
算法原理
KMP中,在比较S[i],P[j]时,i指针会走到S串尾不回溯,而只回溯j指针达到快速匹配目的,其是通过KMP的核心next[]数组做到,回溯时next[]数组会给出j回溯的位置。
next数组
next[i+1] = P[i]之前(包括i)相同前后缀的最大长度
,从而做到在S[i+1]!=P[j]
后回溯时,通过j = next[j]
就可以回到P[j-1]之前(包括j-1)相同前后缀的前缀末端+1的位置开始匹配。
我们可以通过如下代码得到next[]数组,复杂度为O(m):
void getNext(char *p,int plen){ Next[0] = -1; for(int i = 0;i<plen-1;i++){ int j = Next[i]; while(~j && p[i] != p[j]) j = Next[j]; Next[i+1] = ~j?j+1:0; } } //next[i+1] = p[i]之前(包括i)的公共前后缀最大长度,j代表前缀末端 //每次在p[i]匹配前后缀时,若匹配则在next[i+1]存放前缀长度j+1(即从这个位置可回溯并匹配的最近下标),不匹配则回溯到next[j](最近的公共前后缀串下标+1)
用next[]数组进行匹配
得到next数组后,我们只需要在S[i],P[j]匹配失败时将指针j回溯到next[j]的位置,继续匹配即可。
实现如下,复杂度为O(n):
int kmp(char *s,char *p){ int cnt = 0; int slen = strlen(s); int plen = strlen(p); getNext(p,plen); int j = 0; for(int i = 0;i<slen;i++){ while(~j && s[i] != p[j]) j = Next[j]; j++; if(j == plen){ ans[cnt++] = i - plen + 1; i--; j = Next[j-1]; } } return cnt; } //求next数组 //匹配字符串s,p.在s[i]处不匹配,则j=next[j]回溯到最近的公共前后缀串下标+1处(即用s已匹配部分的后缀去找p的已匹配部分的前缀相同的最大长度并从后继续匹配) //在s[i]匹配,则i+1,j+1继续匹配 //匹配完一个,j=next[j]回溯到最近的公共前后缀串下标+1处,继续匹配
完整代码
#include <bits/stdc++.h> #define TLE std::ios::sync_with_stdio(0),cout.tie(0),cin.tie(0) using namespace std; char s[1010],p[1010]; int Next[1010]; int ans[1010]; void getNext(char *p,int plen){ Next[0] = -1; for(int i = 0;i<plen-1;i++){ int j = Next[i]; while(~j && p[i] != p[j]) j = Next[j]; Next[i+1] = ~j?j+1:0; } cout<<"Next:"; for(int i = 0;i<plen;i++){ cout<<Next[i]<<' '; } } //next[i+1] = p[i]之前(包括i)的公共前后缀最大长度,j代表前缀末端 //每次在p[i]匹配前后缀时,若匹配则在next[i+1]存放前缀长度j+1(即从这个位置可回溯并匹配的最近下标),不匹配则回溯到next[j](最近的公共前后缀串下标+1) int kmp(char *s,char *p){ int cnt = 0; int slen = strlen(s); int plen = strlen(p); getNext(p,plen); int j = 0; for(int i = 0;i<slen;i++){ while(~j && s[i] != p[j]) j = Next[j]; j++; if(j == plen){ ans[cnt++] = i - plen + 1; i--; j = Next[j-1]; } } return cnt; } //求next数组 //匹配字符串s,p.在s[i]处不匹配,则j=next[j]回溯到最近的公共前后缀串下标+1处(即用s已匹配部分的后缀去找p的已匹配部分的前缀相同的最大长度并从后继续匹配) //在s[i]匹配,则i+1,j+1继续匹配 //匹配完一个,j=next[j]回溯到最近的公共前后缀串下标+1处,继续匹配 int main(){ TLE; cin>>s>>p; int cnt = kmp(s,p); cout<<endl<<"Match:"<<cnt<<endl; cout<<"Location:"; for(int i = 0;i<cnt;i++){ cout<<ans[i]<<' '; } return 0; }
本文来自博客园,作者:空白菌,转载请注明原文链接:https://www.cnblogs.com/BlankYang/p/15832116.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧