KMP学习笔记

KMP学习笔记

本质

模式串前缀的真前缀和真后缀最大相同的位置

当我们的模式串匹配到s[i]时失配,就说明s[1]~s[i-1]匹配成功,如果此时不直接将模式串右移至kmp[i],而是重新从模式串的s[1]开始匹配是一定会失败的。(因为如果成功了,那么kmp[i]势必比原来大)。所以kmp数组可以帮助我们排除一定匹配失败的情况(有点像斜率优化dp的最佳决策点)。与此同时,从s[1]到s[kmp[i]]是已经匹配成功的(因为这段前缀和s[1]到s[i]的后缀相同),所以可以直接忽略,减少了不必要的比较。

构造——KMP数组怎么来的

kmp[i]用于记录在第i位失配后应该把模式串的哪一位移动到i,显而易见kmp[0]和[1]都是1,后面的又该怎么推出来呢?我们可以利用dp的思想,用两个指针i,j来维护。

假设当前匹配到s[i],kmp[i-1]=j。

  • 如果匹配成功,那么由于j的继承性,kmp[i]=j+1;

  • 如果匹配失败,我们是不是就要重新匹配呢>_< 并不是,我们拿来匹配的模式串其实是s前缀s[i]的前缀,注意到我们用模式串匹配时也是不断尝试匹配s的前缀,所以求kmp数组的本质是用s自己匹配自己!如此一来,我们就可以直接跳到kmp[j]从而减少不必要的运算。

代码实现

#include<iostream>
#include<cstring>
#define MAXN 1000010 
using namespace std;
int kmp[MAXN];
int la,lb,j;
char a[MAXN],b[MAXN];
int main(){
	cin>>a+1;
	cin>>b+1;
	la=strlen(a+1);
	lb=strlen(b+1);
	for(int i=2;i<=lb;i++){
		while(j&&b[i]!=b[j+1]){
			j=kmp[j];
		}
		if(b[i]==b[j+1]){
			j++;
			kmp[i]=j;
		}
	}
	j=0;
	for(int i=1;i<=la;i++){
		while(j>0&&b[j+1]!=a[i]){
			j=kmp[j];
		}
		if(b[j+1]==a[i]){
			j++;
		}
		if(j==lb){
			cout<<i-lb+1<<endl;
			j=kmp[j];	
		}
	}
	for (int i=1;i<=lb;i++)
    	cout<<kmp[i]<<" ";
} 
posted @ 2022-10-21 10:07  狐适之  阅读(46)  评论(1)    收藏  举报