KMP是用来解决字符串匹配问题和字符串循环节问题。
给出两个字符串s1和s2,长度分别为len1和len2,问在s1中是否存在长度为len2的字串s3,使得s3与s2完全匹配,并返回最开始匹配的位置。
按照一般最为暴力的做法就是一个一个的匹配,相当于在s1串中找s2串,最坏时间复杂度为O(len1*len2),难以接受。。。
这里模式串是s1,匹配串是s2。
于是KMP算法应运而生,它可以在O(len1+len2)的时间复杂度内解决这个问题。KMP算法在匹配过程中是通过不停的跳转匹配串的下一个匹配的位置来实现的。
在该算法中最重要的就是next数组了,next[i]表示s2串中前面长度为i的字串的公共最长前缀和后缀长度。
一旦在匹配的时候在某个位置失配之后,直接依靠next数组跳到相同后缀的前缀部分。。(相当于后缀的下一个匹配失败,则直接将位置跳到前缀的部分,因为我们next数组已经预处理了,O(1)实现。),这时候继续比较该位置和前缀的下一个字母是否匹配,不匹配继续按上面的步骤跳转,匹配则匹配下一个字母直到完全匹配。
总而言之,算法的关键就在于next数组,它预处理了匹配串在当前位置匹配失败时直接跳转到下一个前面与模式串前面相同的位置,这就是为什么next数组求的是最长公共前缀和后缀,匹配失败相当于后缀的下一个匹配失败了,我们就可以直接跳到与后缀相同的前缀,此时前面依然是完全匹配的,继续比较下一个。
-------------------------------------KMP一开始都是比较难理解的,多看几次---------------------------------------------
下面上一道例题,模板题。。。。POJ - 3461 OULIPO
题意:给两个字符串,问第一个字符串在第二个字符串中出现了多少次?
#include<iostream>
#include<cstring>
#include<string>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<queue>
#include<vector>
#include<ctime>
using namespace std;
#define up(i,j,k) for(int i=j;i<k;i++)
#define uup(i,j,k) for(int i=j;i>=k;i--)
#define mem(i,j) memset(i,j,sizeof i)
#define ll long long
#define sfi(i) scanf("%d",&i)
#define sfl(i) scanf("%lld",&i)
#define sfs(i) scanf("%s",&i)
#define sfd(i) scanf("%lf",&i)
#define sfc(i) scanf("%c",&i)
#define sfu(i) scanf("%I64u",&i);
#define mod(x) x%mod
#define sf() cout<<"This is a flag!!"<<endl;
#define ull unsigned long long
const int mod=1e9+7;
const double eps=1e-5;
const int MAX=1e6+10;
const double pi=acos(-1.0);
const int inf=0x7f7f7f7f;
const ll INF=0x7f7f7f7f7f7f7f7f;
char arr1[MAX];
char arr2[MAX];
int nxt[10005];
void get_nxt()
{
int len=strlen(arr1);
mem(nxt,-1);
int k=-1;
up(i,1,len)
{
if(k>-1&&arr1[i]!=arr1[k+1])
{
k=nxt[k];
}
if(arr1[i]==arr1[k+1])
{
k++;
}
nxt[i]=k;
}
}
void kmp_cnt()
{
int ans=0;
int len1=strlen(arr1);
int len2=strlen(arr2);
int k=-1;
up(i,0,len2)
{
while(k>-1&&arr1[k+1]!=arr2[i])
k=nxt[k];
if(arr1[k+1]==arr2[i])
k++;
if(k==len1-1)
{
ans++;
k=nxt[k];
}
}
printf("%d\n",ans);
}
int main()
{
int T;
sfi(T);
while(T--)
{
sfs(arr1);
sfs(arr2);
get_nxt();
int len=strlen(arr1);
kmp_cnt();
}
return 0;
}
重点看get_nxt()函数。
在获得next数组的时候,也运用了KMP匹配时的思想----在下一个字母(比如说位置i)匹配失败的时候,就跳到上一个长度(比如说长度i-1)的最长公共前缀和后缀,使得当前匹配位置的前面某一段长度字符串与某一段长度的前缀保持一样。
----------------------------------------------------------理解时间。。----------------------------------------------------------
next数组还有一个循环节的功能,当一个字符串存在某个最小长度的循环节,比如说aaaaaa,循环节为1:a。使用len-next[len]就可以获得该字符串最小长度的循环节。
例题:POJ - 2406
上代码:
#include<iostream>
#include<cstring>
#include<string>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<queue>
#include<vector>
#include<ctime>
#include<fstream>
using namespace std;
#define up(i,j,k) for(int i=j;i<k;i++)
#define uup(i,j,k) for(int i=j;i>=k;i--)
#define mem(i,j) memset(i,j,sizeof i)
#define ll long long
#define sfi(i) scanf("%d",&i)
#define sfl(i) scanf("%lld",&i)
#define sfs(i) scanf("%s",&i)
#define sfd(i) scanf("%lf",&i)
#define sfc(i) scanf("%c",&i)
#define sfu(i) scanf("%I64u",&i);
#define mod(x) x%mod
#define sf() cout<<"This is a flag!!"<<endl;
#define ull unsigned long long
const int mod=1e9+7;
const double eps=1e-5;
const int MAX=1e6+10;
const double pi=acos(-1.0);
const int inf=0x7f7f7f7f;
const ll INF=0x7f7f7f7f7f7f7f7f;
char arr[MAX];
int nxt[MAX];
int len;
void get_nxt()
{
mem(nxt,-1);
int k=-1;
up(i,1,len)
{
while(k>-1&&arr[i]!=arr[k+1])
{
k=nxt[k];
}
if(arr[i]==arr[k+1])
{
k++;
}
nxt[i]=k;
}
}
int main()
{
while(~sfs(arr))
{
if(arr[0]=='.')
break;
len=strlen(arr);
get_nxt();
if(len%(len-nxt[len-1]-1)==0&&nxt[len-1]!=-1)
{
printf("%d\n",len/(len-nxt[len-1]+-1));
}
else
printf("1\n");
}
return 0;
}
个人感觉KMP讲起来比较费劲。。还是需要各位看官去好好理解,好好思考一下。