浅谈KMP
前言
\(\quad\)在考试的时候发现有部分分是KMP的,但是这个久远的记忆早就被我遗忘了,所有就来补一发。我堂堂CCF6级选手竟然会忘记这种东西!\doge
KMP是什么
\(\quad\)KMP是一种快速匹配文本串和模式串的一种算法,它可以在\(O(N+M)\)的时间内完成匹配,并且可以顺手求出这三个东西:
- 模式串是否在文本串中出现。
- 模式串在文本串中出现了多少次。
- 模式串每次在文本串中出现的位置。
\(\quad\)其实简单来说KMP就是一种暴力匹配的优化,可以非常好的解决暴力被数据卡的问题,同时失配的思想也非常广泛的利用到了许多地方然而我只知道AC自动机。
Part1 失配
\(\quad\)任何一个讲KMP的文章都绕不开这个叫做失配的思想和失配指针的东西,那么这个东西到底是个什么呢?
\(\quad\)显而易见,失配指正是基于失配思想的一种产物,如果我们直接来讲失配思想可能会让人一头雾水,所有我们还是从这个比较具体的东西入手。
\(\quad\)那么这个失配指针是什么东西呢,我们思考一下这个过程:模式串一直一直匹配,突然有一个不成功了,那么我们应该怎么做?
\(\quad\)一般情况下,我们都会是从下一个位置开始,再次进行匹配。但是我们或许可以再思考一下,有没有什么优化的方法?于是我们就想到了一个小小的细节:如果同样还是这一个位置,模式串前面有一个子序列和从模式串某处到现在前面的位置的子序列是相同的,那么我们就可以就可以考虑直接从上述的子序列开始匹配,这样就可以节省一大部分时间。
\(\quad\)自我感觉上方的解释还是有点不好理解,于是我们就再换一个更好理解的东东:我们发现我们现在没有办法匹配了,但是发现模式串前面有一段和可以放进当前位置并且可以完成匹配,我们就可以直接退回到模式串上的这个位置,然后继续匹配。
\(\quad\)但是我们会发现一个问题,就是我们无法知道对于文本串的每一个点,失配时我们应去跳到哪一个模式串上的点。所有我们就需要在模式串上下功夫。我们会发现如果我们模式串从开头到\(a\)点和从\(b\)点到\(c\)点是一样的话,那么当我们在\(c+1\)点失配的时候,因为\(b\)点到\(c\)点已经确保匹配了,那么开头到\(a\)点也可以确保匹配了,我们从\(a+1\)点开始匹配也不会出现什么问题。确认位置的指针就是失配指针。
Part2 匹配
\(\quad\)完成了失配之后我们就要来解决匹配的问题了,其实有了失配的思想,匹配也会相对更好理解了,我们从头向后扫文本串,每一次失配就改变模式串匹配的位置,每一次完成匹配也改变模式串的位置,直到匹配完成。
code
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<algorithm>
#include<cmath>
#include<ctime>
#include<climits>
#include<sstream>
using namespace std;
const int N = 1e6 + 10;
string s , t;
int net[N];
inline void getnet()
{
net[0] = 0;
for(register int i = 1 , j = 0 ; i < t.size() ; i++)
{
while(j > 0 && t[i] != t[j]){j = net[j - 1];}
if(t[i] == t[j]){j++;}
net[i] = j;
}
}
int ans = 0;
inline void kmp()
{
for(register int i = 0 , j = 0 ; i < s.size() ; i++)
{
while(j > 0 && s[i] != t[j]){j = net[j - 1];}
if(s[i] == t[j]){j++;}
if(j == t.size()){ans++ ; j = net[j - 1];}
}
}
int main()
{
ios::sync_with_stdio(false);
cin >> s >> t;
getnet();
kmp();
cout << ans;
return 0;
}
这就是简单易懂的KMP了!