KMP字符串匹配问题
KMP算法
本文参考资料:https://www.zhihu.com/question/21923021
KMP算法是一种字符串匹配算法,可以在
字符串匹配问题#
首先我们需要了解一下什么是字符串匹配问题。
比如给你两个串
我们可以发现,对于字符串 no 的匹配结果是在主串中出现了 1 次,位置为 6,对于字符串 ob 的匹配结果就是在主串中出现过 2 次,出现的位置分别为 1 和 10。
生活中有很多类似的问题,比如当你在成绩单里找自己的名字或者说在小说的章节里面查找你所想看的带有某个关键字的部分,都是和这个是差不多的。
Brute-Force#
直译过来就是暴力破解的意思,所以这个算法也是非常的暴力,首先,我们应该如何实现两个字符串
那么我们理解了如何判断两个字符串相等的话,我们就可以想到,假设要查询
当然这个算法的复杂度是很大的,在一般情况下是会 T 飞的,所以我们需要对其进行改进。
Brute-Force的改进#
上图为 Brute-Force 的最坏情况,可以看到复杂度并不乐观,所以有了这一部分。
我们很难降低字符串比较的复杂度(因为比较两个字符串,真的只能逐个比较字符)。因此,我们考虑降低比较的趟数。如果比较的趟数能降到足够低,那么总的复杂度也将会下降很多。要优化一个算法,首先要回答的问题是“我手上有什么信息?” 我们手上的信息是否足够、是否有效,决定了我们能把算法优化到何种程度。请记住:尽可能利用残余的信息,是 KMP 算法的思想所在。
在 Brute-Force 中,如果从
所以我们可以发现,以从
这时候我们需要一个新的东西——next 数组。
上图给出了一个例子。
在
回忆
如上图所示,绿色部分是成功匹配,失配于红色部分。深绿色手绘线条标出了相等的前缀和后缀,其长度为
然后我们如何快速的求出
我们可以想到,我们可以用某种暴力手段慢慢求,但是那样太慢了,没有效率,所以我们要用一些比较奇♂妙的方式来快速计算出
for(int i=2;i<=lenb;i++)//处理next数组,过程相当于拿自己和自己匹配
{
while(j&&b[i]!=b[j+1])j=next[j];//如果已经开始匹配上了但是当前点不同,那就往后移动
if(b[i]==b[j+1])j++;//如果能匹配上,指针右移
next[i]=j;//存放当前点的最长公共前后缀长度
}
最后我们就需要进行模式串与主串的匹配,和上面差不多的道理,就是当每一次匹配失败后直接利用
P3375 【模板】KMP字符串匹配#
code:
#include<bits/stdc++.h>
#define next Next
#define N 1000100
using namespace std;
int next[N],lena,lenb,j;
string a,b;
signed main()
{
cin>>a>>b;
lena=a.size();
lenb=b.size();
a.insert(a.begin(),' ');
b.insert(b.begin(),' ');//前面插入一个空格使其下标从1开始
for(int i=2;i<=lenb;i++)//处理next数组,过程相当于拿自己和自己匹配
{
while(j&&b[i]!=b[j+1])j=next[j];//如果已经开始匹配上了但是当前点不同,那就往后移动
if(b[i]==b[j+1])j++;//如果能匹配上,指针右移
next[i]=j;//存放当前点的最长公共前后缀长度
}
j=0;//清空j
for(int i=1;i<=lena;i++)//开始匹配
{
while(j&&b[j+1]!=a[i])j=next[j];//如果当前开始匹配但是当前点不同,就直接将b串后移
if(b[j+1]==a[i])j++;//如果当前点能匹配上,指针右移
if(j==lenb)//如果完全匹配上了
{
cout<<(i-lenb+1)<<endl;//输出开始的下标
j=next[j];//继续往后匹配
}
}
for(int i=1;i<=lenb;i++)
cout<<next[i]<<" ";//输出
return 0;
}
P2375 [NOI2014] 动物园#
这题的题解好不容易才看明白赶紧记录一下
首先我们需要先和正常的一样求出
从上面可以看到,当前串的公共前后缀串的公共前后缀串也是当前串的公共前后缀串,所以我们可以像递推一样把所有的公共前后缀串的数量递推出来,但是,这里面是包含重叠的,这时我们可以发现,如果是第
#include<bits/stdc++.h>
#define int long long
#define P 1000000007
#define next Next
#define N 1000100
using namespace std;
int T,n,next[N],j,ans,sum[N];
string s;
signed main()
{
cin>>T;
while(T--)
{
ans=1;j=0;
sum[1]=1;
cin>>s;
n=s.size();
s.insert(s.begin(),' ');
for(int i=2;i<=n;i++)
{
while(j&&s[i]!=s[j+1])j=next[j];
if(s[i]==s[j+1])j++;
next[i]=j;
sum[i]=sum[j]+1;
}
j=0;
for(int i=2;i<=n;i++)
{
while(j&&s[i]!=s[j+1])j=next[j];
if(s[i]==s[j+1])j++;
while(j*2>i)j=next[j];
ans=(ans*1ll*(sum[j]+1))%P;
}
cout<<ans<<endl;
// for(int i=1;i<=n;i++)
// cout<<next[i]<<" ";
// cout<<endl;
// for(int i=1;i<=n;i++)
// cout<<sum[i]<<" ";
// cout<<endl;
}
return 0;
}
P4824 [USACO15FEB] Censoring S#
这道题目需要进行字符串匹配,所以想到KMP算法,打开标签发现有栈,所以想到用栈
我们可以发现我们如果要是想要防止前面的串当删除其中的子串后与后面的串接成新的串,最暴力的办法就是每删除一个串就往前跳
#include<bits/stdc++.h>
#define int long long
#define next Next
#define N 1000100
using namespace std;
int len1,len2,next[N],j,f[N],stk[N],top;
string s1,s2;
signed main()
{
cin>>s1>>s2;
len1=s1.size();
len2=s2.size();
s1.insert(s1.begin(),' ');
s2.insert(s2.begin(),' ');
for(int i=2;i<=len2;i++)
{
while(j&&s2[i]!=s2[j+1])j=next[j];
if(s2[i]==s2[j+1])j++;
next[i]=j;
}
j=0;
for(int i=1;i<=len1;i++)
{
while(j&&s1[i]!=s2[j+1])j=next[j];
if(s1[i]==s2[j+1])j++;
f[i]=j;
if(j==len2)j=f[stk[(top-=len2-1)]];
else stk[++top]=i;
}
for(int i=1;i<=top;i++)
cout<<s1[stk[i]];
cout<<endl;
return 0;
}
作者: 北烛青澜
出处:https://www.cnblogs.com/Multitree/p/17067647.html
本站使用「CC BY 4.0」创作共享协议,转载请在文章明显位置注明作者及出处。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!