在长度为m的模式串和长度为n的主串的匹配中,如果纯用双指针暴力匹配,那么在主串上的指针i需要反复回溯,这样的时间复杂度为O(mn)。
每次匹配失败的过程中,只要j指针往后移动了,就说明此时模式串和主串的一部分是匹配成功的,这部分主串的信息我们是已知的,如果此时i不动,而让j移动到另一位置k,且满足模式串上的1~j-1与(k-j+1)~(k-1)这两部分是完全一致的,则下一次匹配可以让i不动,而使模式串直接从k开始即可,这样可以大大的减小时间复杂度。
因此,我们就明白,KMP算法主要分为两步,第一步是求j移动到另一位置k的值,我们不妨令k = next[j],即求next数组,这一部分仅仅与模式串有关,而与主串无关,时间复杂度为O(m)。
代码如下:(下标略有区别,含义一致)
//模式串p长m
for (int i = 2, j = 0; i <= m; i++)
{
//只要匹配失败,j就不停地移到next[j]直至这一位相同,即可进行下一次匹配
while (j != 0 && p[i] != p[j + 1])
j = ne[j];
if (p[i] == p[j + 1])
j++; // j指针往后移
ne[i] = j; //计算next数组
}
第二步就是匹配。每次匹配失败,就令j=next[j]即可进行下一次匹配,时间复杂度为O(n)。j如果等于0了,说明此时退无可退,直接i++进行下一次匹配了。
代码如下:(下标略有区别,含义一致)
//主串s长n,模式串p长m
for (int i = 1, j = 0; i <= n; i++)
{
//只要匹配失败,j就不停地移到next[j]直至这一位相同,即可进行下一次匹配
while (j != 0 && s[i] != p[j + 1])
j = ne[j];
if (s[i] == p[j + 1])
j++; // j指针往后移
//移到了最后一位m-1的下一位,说明匹配成功
if (j == m)
{
j = ne[j];
//匹配成功
}
}
题目链接:KMP字符串
AC代码如下:
#include <bits/stdc++.h>
using namespace std;
const int M = 1e5 + 10, N = 1e6 + 10;
char p[M], s[N]; // p为模式串,s为主串
int ne[M]; // next数组,ne[1]=0,ne[j]<j
int m, n;
int main()
{
cin >> m >> p + 1 >> n >> s + 1; // p与s都习惯性地从1开始,要往后移一位
//求next数组
for (int i = 2, j = 0; i <= m; i++)
{
//只要匹配失败,j就不停地移到next[j]直至这一位相同,即可进行下一次匹配
while (j != 0 && p[i] != p[j + 1])
j = ne[j];
if (p[i] == p[j + 1])
j++; // j指针往后移
ne[i] = j; //计算next数组
}
//匹配
for (int i = 1, j = 0; i <= n; i++)
{
//只要匹配失败,j就不停地移到next[j]直至这一位相同,即可进行下一次匹配
while (j != 0 && s[i] != p[j + 1])
j = ne[j];
if (s[i] == p[j + 1])
j++; // j指针往后移
if (j == m) //匹配成功
{
j = ne[j];
cout << i - m << ' '; //打印主串上匹配成功的子串起始位置为所求
}
}
cout << endl;
}