KMP算法
目录
首先先了解一些定义
1.前缀:不包含尾字符的所有字串
2.后缀:不包含首字符的所有字串
3.前缀表:用于存放最长的相等的前后缀,例如:
对于字符串 s[6]=aadaad
`"a"
最大想等前后缀为
"a"则
next[0]=1`
"aa"
最大想等前后缀为"aa"
则next[1]=2
"aad"
最大想等前后缀为""
,因为前缀一定不存在"d"
,而后缀一定有,所以next[2]=0
"aada"
最大想等前后缀为"a"
则next[3]=1
"aadaa"
最大相等前后缀为"aa"
,则next[4]=2
"aadaad"
最大想等前后缀为"aad"
,则next[5]=3
作用方式
那么前缀表到底有什么用呢,我在学习这个的时候觉得这个想法真的泰天才啦!
先来看朴素情况会有什么问题
举一个简单的例子,子串为"abdc"
,主串为"abdabdc"
朴素算法下我们在对比到第四位,即在主串上从a开始,在找第四位为c时,发现主串的第四位是d而不是c.因此说明前四位并不是我们要找的子串,所以就把放在主串上的指针往后移一位,到第二个b
但是对于这个简单的例子,我们可以很容易看出,这个操作是多余的,因为对于子串"abdc"
,如果主串的前三位不是我要找的子串,那么前三位就不可能会有一个字符和子串的首位相等,也就是我们在做的,子串的前后缀最大相等的长度是0.
再来回顾一下最大相等前后缀是啥意思,例如上面的例子,我们子串的最大相等前后缀长度为0,就是不存在前后相等的部分,那么在主串中和子串成功匹配的前三位就可以跳过了 ! ! !,也就是说对于朴素算法,我第一次失败后要在第二位b开始继续找.而我们完全可以从第四位的"a"开始找,直接节省了两次无意义的搜索!
cpp实现:
#include <iostream>
#include <stack>
#include <string>
#include<map>
using namespace std;
const int N = 1000010;
int nex[N];
char s1[N], s2[N];
int n, m;
int main()
{
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> n >> s1 + 1 >> m >> s2 + 1;//下标从1开始
for (int i = 2, j = 0; i <= n; i++)//i是几位的子串,j是用来计算长度的
{
while (j && s1[i] != s1[j + 1])j = nex[j];//若不相等,则回退j
if (s1[i] == s1[j + 1])j++;//进位
nex[i] = j;//保存
}
for (int i = 1, j = 0; i <= m; i++)
{
while (j && s2[i] != s1[j + 1])j = nex[j];//若和子串不相等则回退
if (s2[i] == s1[j + 1])j++;//若相等就再进一位!!!
if (j == n)//连着n个字符相等就是找到子串啦!
{
cout << i - n <<" ";
j = nex[j];//这里并不选择归零,而是选择完整子串的最大前后缀长度,因为会出现aadaa这种对于完整子串存在重复的前后缀,对于aadaadaadaa来说就是存在三个子串(我在这卡了好久)
}
}
return 0;
}