AcWing 831. KMP字符串
所谓字符串匹配,是这样一种问题:“字符串 P 是否为字符串 S 的子串?如果是,它出现在 S 的哪些位置?” 其中 S 称为主串;P 称为模式串。下面的图片展示了一个例子。
暴力做法:
- 双重循环
双重循环劣势如下:
- 双重循环的时候,如果
主串S
和模式串P
不匹配,则如果S会上次匹配地方为i,那么本次匹配时,S串会从i+1
开始匹配。 - 对于暴力算法,如果出现不匹配字符,同时回退
S
和P
的指针,嵌套 for 循环,时间复杂度 O ( M N ) O(MN) O(MN),空间复杂度 O ( 1 ) O(1) O(1)。最主要的问题是,如果字符串中重复的字符比较多,该算法就显得很蠢。
比如 txt = “aaacaaab” pat = “aaab”:
而kmp算法有两个精髓:
- KMP 算法永不回退 txt 的指针 i,不走回头路(不会重复扫描 txt),而是借助 next数组中储存的信息把 pat 移到正确的位置继续匹配,时间复杂度只需 O(N),用空间换时间。
next数组
只和模式串pat
有关系。
具体kmp如何优化的请看:kmp演示
具体kmp代码和next数组请看:next求解和匹配操作
最为精髓的是:
然后来说明一下next数组的含义:对next[ j ] ,是pat[ 1, j ]串中前缀和后缀相同的最大长度(部分匹配值),即 pat[ 1, next[ j ] ] = pat[ j - next[ j ] + 1, j ]。
next数据应用举例:
那么next如何求出:
- 通过对模式串和搜集
- 类似递归,不能匹配的话,j=ne[j],再不能匹配则j=ne[ne[j]]
//求next[]数组, i为遍历模式串的指针,j为模式串匹配相同的字母个数
for(int i = 2, j = 0; i <= m; i++)
{
//不能匹配
while(j && p[i] != p[j+1]) j = ne[j];
//匹配上了
if(p[i] == p[j+1]) j++;
ne[i] = j;
}
例题
https://www.acwing.com/problem/content/833/
完整代码与定义说明:
- ne为next数组,next在cpp中定义了
- 模式串中使用 指针j+1 和 字母串的指针 i 作比较
#include <iostream>
using namespace std;
const int N = 100010, M = 10010; //N为模式串长度,M匹配串长度
int n, m;
int ne[M]; //next[]数组,避免和头文件next冲突
char s[N], p[M]; //s为模式串, p为匹配串
int main()
{
cin >> n >> s+1 >> m >> p+1; //下标从1开始
//求next[]数组
for(int i = 2, j = 0; i <= m; i++)
{
while(j && p[i] != p[j+1]) j = ne[j];
if(p[i] == p[j+1]) j++;
ne[i] = j;
}
//匹配操作
for(int i = 1, j = 0; i <= n; i++)
{
while(j && s[i] != p[j+1]) j = ne[j];
if(s[i] == p[j+1]) j++;
if(j == m) //满足匹配条件,打印开头下标, 从0开始
{
//匹配完成后的具体操作
//如:输出以0开始的匹配子串的首字母下标
//printf("%d ", i - m); (若从1开始,加1)
j = ne[j]; //再次继续匹配
}
}
return 0;
}