目录
一.KMP使用方向:
KMP算法主要用于快速确定目标字符串是否存在于样板字符字符串中。相较于暴力匹配法,KMP引入了next数组,记录应该移动到的最合适位置。
二.一般暴力法(BF算法):
将目标串从第一位逐次向后移动,和样本串相对应逐字进行比较。
如样板串:abcdefgh 目标串:fgh
首先和样板串前三个字母abc对比,发现对不上;样板串往后退一位,和bcd进行对比,又没有对上;按这个方式对比到fgh时,发现对比上了,因此目标串存在。
在看一个演示代码:
string T="思念豫乡的明月,所以叫豫乡明月";
string P="豫乡明月";
int BF(string tString,string pString)
{
int i=0; //主串中的位置
int j=0; // 模式串中的位置
for(;i<=tString.size()-pString.size();i++)
{
bool isPatt= true;
for(;j<pString.size();j++)
{
if(tString[i+j]!=pString[j])
{
isPatt= false;
break;
//一旦不匹配,主串位置后退到i,模式串位置j回退到0;
}
// 当两个字符相同,就比较下一个
}
if(isPatt) return i;
}
return -1;
}
int main()
{
cout<<BF(T,P);
}
三.NEXT数组:
如果不使用逐一配对的方法,就要思考如何一次移动更多位次。我们就要前缀和后缀的关系。如果部分前后缀相等,那么,如果后缀后的一个元素与样板串不匹配,那么可以直接将与后缀相同的前缀放到后缀的位置,从前缀后第一个元素开始比对。
前缀:从第一个元素开始,往后若干连续元素
后缀:从最后一个元素开始,往前若干连续元素
样本串:aabaabaabaaf 目标串:aabaaf关于前后缀:
a 0(只有一个元素)
aa 1(前缀a,后缀a,相同数目为1)
aab 0(第一个元素和最后一个元素不同)
aaba 1
aabaa 2(前缀aa,后缀aa)
aabaaf 0
首先和样本串开头进行匹配aabaa,然后下一个元素 b ,匹配不上,开始查找aabaa前缀和,前缀和是2,所以让前缀和后的元素和b 进行比对,发现可以配对上。
那如何确定跳到哪里呢,这时要引入next数组。
(1)next数组针对的是目标串。
(2)next[10]的含义是针对s[0]~s[10]这11个元素构成的字符串的相同前后缀长度-1(减一的目的是下标从0开始)。
看以下next数组的定义方法:
void Next(string p){
int plen = p.size();
gnext[0] = -1; //起始状态设置为-1,因为数组下标从0开始
int k = -1; //起始时将k,j的差设置为1
int q = 0;
while(q < plen - 1){
if(k == -1 || p[q] == p[k]){ //k = -1 表示刚已经到达前缀的起始点
++k;
++q;
gnext[q] = k; //找相同前后缀
}
else{
k = gnext[k]; //不匹配就回到最前面
}
}
}
开始k是-1的原因是保证++k的第一个元素是0。
四.KMP写法:
将逐个后退改为前缀移动到后缀位置。
有以下写法:
int KMP(string s,string p){
int slen = s.size();
int plen = p.size();
while(i<slen && j < plen){
if(j == -1 || s[i] == p[j]){
i++;
j++;
}
else{
j = gnext[j]; // 匹配不成功,就将j回到上一个该序列的前面
}
// cout<<"i = "<<i<<" j = "<<j<<endl;
}
if(j = plen)
return plen;
else
return i;
}
完整代码:
#include<iostream>
using namespace std;
typedef long long ll;
const ll N = 1e5 +5;
ll ans = 0;
string T ;
string P ;
int i = 0,j = 0;
int gnext[N];
void write(int x){
if(x<0){
putchar('-');
x = -x;
}
if(x>9){
write(x/10);
putchar(x%10 + '0');
}
else putchar(x + '0');
return;
}
void Next(string p){
int plen = p.size();
gnext[0] = -1; //起始状态设置为-1,因为数组下标从0开始
int k = -1; //起始时将k,j的差设置为1
int q = 0;
while(q < plen - 1){
if(k == -1 || p[q] == p[k]){ //k = -1 表示刚已经到达前缀的起始点
++k;
++q;
gnext[q] = k; //找相同前后缀
}
else{
k = gnext[k]; //不匹配就回到最前面
}
}
}
int KMP(string s,string p){
int slen = s.size();
int plen = p.size();
while(i<slen && j < plen){
if(j == -1 || s[i] == p[j]){
i++;
j++;
if(ans<j) ans = j; //记录最长前缀能符合多长
}
else{
j = gnext[j]; // 匹配不成功,就将j回到上一个该序列的前面
}
// cout<<"i = "<<i<<" j = "<<j<<endl;
}
if(j = plen)
return plen;
else
return i;
}
int main(){
cin>>T>>P;
Next(P);
KMP(T,P);
// write(KMP(T,P));
// puts("");
write(ans);
}