学习笔记:KMP字符串匹配算法

前言

其实很早就学了,不过还是感觉很迷。但做到了NOIP2020的T2,只有学一下了。

原理

KMP是由3名数学家搞出来的算法,KMP也就是这3位的首字母。

模板传送门luoguP3375
首先看题目要求给定2个字符串,要求其中的一个字符串在另一个字符串里出现的次数。
容易想到暴力:
直接枚举2个串,如果不同,就将去匹配的串从头开始重新从匹配失败的位置继续找。
最坏的时间复杂度是:O(nm)
这个暴力算法叫做BF算法。

我们发现这个算法最主要的问题是每次从头开始重新匹配,其实可以从其中的某个地方开始找,就能减少大量的复杂度。
例如:
s1:ABCABCABD
s2:ABCABD
我们匹配到第6位时匹配失败,此时不难发现:
s1:ABCABCABD
s2:ABCABD
s2可以直接接到加粗的AB的位置继续匹配。
s1:ABCABCABD
s2:----- ABCABD

由于我们搜到了第6位,说明前面的全部是相等的,所以可以不必比较s1和s2,直接在s2上比较前缀和后缀是否相同,由于全部相同,所以就等价在s1上找。

现在就是要找到由于是按顺序找,所以就是找A,AB,ABC,ABCA,ABCAB,ABCABD的前后缀是否相同,使匹配失败后跳转。注意:这里的跳转是指从哪一位开始继续枚举。

通常我们使用nex数组表示失败后要从哪一位开始枚举。
给出通项公式:
n e x [ i ] = { − 1    j = 0 l e n 相 同 的 前 后 缀 的 长 度 0        前 后 缀 不 同 nex[i]=\begin{cases}-1 \qquad \;j=0\\len \qquad 相同的前后缀的长度\\0 \qquad\;\;\; 前后缀不同\end{cases} nex[i]=1j=0len0

代码实现:

void getnex(){
    
    
	int k=-1,j=0;
	nex[0]=-1;
	while(j<tlen){
    
    
		if(k==-1 || t[k]==t[j]) nex[++j]=++k;
		else k=nex[k];
	}
}

匹配也差不多,当发现匹配的字符串已经全部匹配时,就说明匹配好了,匹配的起点就等于扫过了文本串的长度减去模式串的长度加+1。

代码实现:

void KMP(){
    
    
	int i=0,j=0;
	while(i<len && j<tlen){
    
    
		if(j==-1 || a[i]==t[j]) i++,j++;
		else j=nex[j];
		if(j>=tlen) cout<<i-tlen+1<<"\n",j=nex[j];
	}
}

完整代码:

#include<bits/stdc++.h>
using namespace std;

const int N=1e6+5;
int nex[N],tlen,len,ans[N],cnt;
string a,t;

void getnex(){
    
    
	int k=-1,j=0;
	nex[0]=-1;
	while(j<tlen){
    
    
		if(k==-1 || t[k]==t[j]) nex[++j]=++k;
		else k=nex[k];
	}
}

void KMP(){
    
    
	int i=0,j=0;
	while(i<len && j<tlen){
    
    
		if(j==-1 || a[i]==t[j]) i++,j++;
		else j=nex[j];
		if(j>=tlen) cout<<i-tlen+1<<"\n",j=nex[j];
	}
}

int main(){
    
    
	cin>>a>>t;
	len=a.size();tlen=t.size();
	getnex();
	KMP();
	for(int i=1;i<=tlen;i++) cout<<nex[i]<<" ";
	return 0;
}

Guess you like

Origin blog.csdn.net/pigonered/article/details/121372569