前言
kmp算法又称模式匹配算法,能够在线性时间内判定字符串A[1-N]是否是字符串B[1-M]的子串。(<=N<M<=1E6)
bf算法像不像你那个不会kmp的女友,她完全不会考虑子串与主串已匹配区间,她只会想着下一个,在从头慢慢匹配,他不会记得你们二个已经匹配成功了k个相同的字符。
一、问题导出
① 字符串匹配例题
② BF解决方案
给定主串数组A[1-M],子串数组P[1-N],分别以i,j为其字符串比较过程中的当前下标。(1<=i<=M,1<=j<=N)
令i起始比较位置为,T。
每次比较A[i]是否等于p[j],等于则i++,j++直至j==n输出位置下标i-j(这里数组都从1开始)。
否则i回退到T+1,j回退到1。
③ 代码实现
时间复杂度O(n*m),空间复杂度O(1)
#include <iostream>
#include <string.h>
#include <algorithm>
using namespace std;
const int N=1e6+10;
int ne[N];
char a[N],p[N];
int n,m;
int main()
{
cin>>n>>p+1>>m>>a+1;
for(int i=1,j=1;i<=m;i++)
{
int k=i;
while(a[k]==p[j])
{
k++;
j++;
if(j==n) cout<<(k-j)<<" ";
}
j=1;
}
return 0;
}
二、引入KMP算法思想
① kmp算法提高效率解释
kmp算法本质上是在BF算法的基础上减少回退次数,达到优化效果。
② kmp算法详解
假设子串与主串前【1-j】都匹配(1<=j<m),但a[i+1]!=p[j+1]。(数组下标都从1开始)
如图:
对此肯定要移动子串,继续与主串进行匹配。
对于BF算法中,遇此情况是,回退到,T+1,j=1;
在此我们对已匹配区间【1-j】性质进行分析,考虑移动多少位。
注:这里说的移动几位,移动后应满足,移动后的区间依然为子串与主串匹配区间,即匹配空间范围是0<=k<=j-1(这里的移位是过滤掉原匹配子串空间移动后成子串不匹配空间,而变成移动后又变成子串匹配空间,但子串匹配空间变小了,由此继续比较a[i+1]是否等于p[j+1]);
假设可以移动一位,就可以满足区间【1-j-1】又是一个子串与主串的匹配区间,
即:
为此,对于能移动k位,即原子串与主串匹配长度区间的字符串要满足前缀与后缀相等,且相等的长度为【j-k】(j为匹配长度,k为移动位数)。
③ 代码实现
#include <iostream>
#include <algorithm>
#include <string.h>
using namespace std;
#define _for(i,j,a,b) for( int i=(a),j=0;i<=(b); ++i)
#define _fo(i,a,b) for( int i=(a);i<=(b); ++i)
#define _scan(a,b) scanf("%d%d",&a,&b)
const int N=1e6+10;
int ne[N];
char p[N],a[N];
int n,m;
int main(void)
{
cin>>n>>(p+1)>>m>>(a+1);
_for(i,j,2,n)
{
while(j&&p[i]!=p[j+1]) j=ne[j];
if(p[i]==p[j+1]) j++;
ne[i]=j;
}
//_fo(i,1,n) cout<<ne[i]<< " ";
//cout<<endl;
_for(i,j,1,m)
{
while(j&&a[i]!=p[j+1]) j=ne[j];
if(a[i]==p[j+1]) j++;
if(j==n)
{
cout<<i-j<<" ";
j=ne[j];
}
}
return 0;
}