题解 P3375 【【模板】KMP字符串匹配】
z3475
2018-08-19 23:25:44
KMP算法主要用于字符串匹配,但是其扩展能解决很多字符串问题。下面我来给大家讲解KMP算法 tips:以下数组均为从0开始
## 1、next数组
next数组的定义是对于一个字符串str,next[i]表示的是在str中
[0,next[i])以及
[i-next[i],i)
这两个区间中的字符严格相等的最大next[i]值(区间不能重叠
举个例子
`a b c a b c b b a b c`
`0 0 0 1 2 3 0 0 1 2 3`
`[a] b c [a] `=> `next[4]` = `1`
`[a] [b] c [a] [b]` => `next[5]` = `2`
`[a] [b] [c] [a] [b] [c]` => `next[6]`=`3`
画一张图意会
![](https://www.z3475.cc/blog/wp-content/uploads/2018/08/temp3-1-300x101.png)
其中的线表示相同的一堆相同的字符,长方形指一个(一堆)字符,中间的省略号省略了一堆字符
手算不难得出,我们先不讲这个数组通过算法怎么生成的,我们先讲怎么用这个数组
## 2、KM
在介绍普通的KM算法前我们先回顾一下解决字符串查找的朴素算法
```cpp
char s1[MAXN],s2[MAXN];//s1.find(s2);
int l1=strlen(s1),l2=strlen(s2);
int j=0,i=0;
while (i!=l1){
if (s1[i]==s2[j]) i++,j++;//匹配
else i-=j-1,j=0;//失配
if (j==l2){
cout << i-j+1 << endl;
j=0;//相当于失配
}
}
```
明显失配时的移动可以优化,那怎么优化呢,我们就要用到`next[]`
根据`next[]`我们可以画一张关于失配图
![](https://www.z3475.cc/blog/wp-content/uploads/2018/08/temp1-1-300x107.png)
最佳的移动是这样
![](https://www.z3475.cc/blog/wp-content/uploads/2018/08/temp2-1-300x86.png)
所以检测到失配时
`i`可以保留,`j` = `next[j-1]`
但是`j`肯定有等于`0`的情况,`j` - `1` 不就越界了吗?
-我们就在计算时加一个F()函数检测`j` - `1`是不是越界了
于是用`next[]`优化后的代码就出来了
```
#define F(x,nxt) (x<0?-1:nxt[x])
int i=0,j=0;
while (i!=l1){
if (j==-1||s1[i]==s2[j]) i++,j++;
else j=F(j-1,nxt);
if (j==l2){
cout << i-j+1 << endl;
j=F(j-1,nxt);
}
}
```
## next[]的计算
考虑以下字符串,我们要算出它的`next[]`
a b c a b c a b
手算得
0 0 0 1 2 3 1 2
显然对于每一个`next[i]`一种情况可以这么弄
`next[i]` = `next[i-1]` + `1` **if** `str[i]`==`str[next[i-1]]`
那当`str[i]`!=`str[next[i-1]]`的情况呢
先给一个结论,然后我们来试着证明它
我们就令`j` = `next[i-1]`
```cpp
while (1){
if (str[i]==str[j]) {next[i] = j + 1;break;} //①
if (str[i]!=str[j]) j = next[j] - 1; //②
}
```
即
`next[i]` = `j` + `1`,`str[i]`==`str[j]` ①
`j` = `next[j]` - `1`,`str[i]`!=`str[j]` ②
一旦执行到①,`next[i]`就算完了
证明过程我们用一张图来意会
![](https://www.z3475.cc/blog/wp-content/uploads/2018/08/temp1-300x44.png)
我们要求箭头指向的那个字符的next值,这里我们假设前面字符的next值已经求完了
但是我们可能会检测到②这种情况,图就变成了这样
![](https://www.z3475.cc/blog/wp-content/uploads/2018/08/temp2-300x50.png)
这样的话显然是正确的,剩下的证明过程,提一个主要思路
1、证明真正的next值<算出来的值是假的
2、证明真正的next值>算出来的值是假的
又在上一章讲到我们还要把next[]往后移一位且next[0]=-1
怼在一起有代码
```cpp
#include<bits/stdc++.h>
#define F(x,nxt) (x<0?-1:nxt[x])
using namespace std;
char s2[1000010],s1[1000010];
int nxt[1000010];
int main(){
scanf("%s%s",s1,s2);
int l1=strlen(s1),l2=strlen(s2);
//计算nxt[]
nxt[0]=0;
for (int i=1;i<l2;i++){
int j=nxt[i-1];
while (j&&s2[i]!=s2[j]) j=nxt[j-1];
if (s2[j]==s2[i]) nxt[i]=j+1;
else nxt[i]=0;
}
//匹配
int i=0,j=0;
while (i!=l1){
if (j==-1||s1[i]==s2[j]) i++,j++;
else j=F(j-1,nxt);
if (j==l2){
cout << i-j+1 << endl;
j=F(j-1,nxt);
}
}
for(int i=0;i<l2;i++) cout << nxt[i] << ' ';
return 0;
}
```