模式串匹配--kmp算法
当然在kmp算法优化之前 ,首先想到的是暴力求解法, 即朴素的模式匹配
朴素的模式匹配
假设我们的主串A=“ABBABBABABAAABABAAA”,子串T=“ABBABAABABAA”,我们需要在主串中找到子串的位置,比较朴素的想法是用两个指针(指针其实也就是下标i,j)分别指向主串和子串,如果两个指针指向的元素相同则指针后移,不相同则需要回退指针(比较指针 p 回退到子串开头位置),重复进行上述操作直到主串指针指向主串末尾,即如下所示
比较指针所指位置,就是和主串中发生不匹配的位置
子串第6个位置和主串不相同
此时,比较指针回退到子串开头位置,这就是指针回溯 ,为了解决这个问题 ,引进了kmp算法
kmp算法
kmp算法中最重要的就是求next[i]数组 , next[i]数组中存放的是 最长公共前后缀的长度 , 接下来通过图解来剖解kmp算法
kmp算法步骤
step1:先逐个遍历比较子串和主串 , 当比较停止时 , 比较指针指向不相同元素的位置为 p(如下图打叉的位置 p = 6)
step2:寻找子串中 p 左边的最长公共前后缀
最长公共前后缀 为 AB
step3:直接往后移动子串, 使得公共前后缀里的 前缀 移动到 原先的 后缀 的位置 ,保证当前比较指针所指的位置左边 上下是匹配的
由此我们可以得知当 最长公共前后缀 等于2时, 我们拿子串的 3号位 与 主串进行 比较(下图) , 那么next[n] = 子串的第 n 个位置与主串进行比较 , n 为比较指针 p 的位置(即为出现不同的位置)
如图:next[6]=3 详细的规律放在后面阐述
注意:子串和主串 从下标1 开始存
kmp算法的优点
从以上述步骤我们可以惊喜的发现 , 在使用朴素算法进行匹配时,比较指针需要需要进行一些回退;而在知道了子串的一些性质后,主串指针不需要再进行回退,只需一直往前走就行,这样就节省了一些时间开销
补充一下找最长公共前后缀 需要注意的点: 公共前后缀的长度 要小于 比较指针左边的子串长度 ,如果相同就无法移动子串了,如下图
最重要的就是求 next [] 数组
next[i] 的数组原理
最重要的一点 : next [ ]数组的数值 只与子串本身有关
next [ i ] = j,下标为 i 的字符前的字符串最长相等前后缀的长度为 j + 1
求next数组的时候,是子串在自己运算,求出每一个数所在位置的next的值,和主串没有关系
结论: next 的 值为 当前比较指针p 前子串 的公共前缀和 长度+1(图1,2)
下标为 p 的位置(图1)
规律:假如公共前后缀的长度为a,那就是第a+1号位与主串当前位开始作比较
注意当公共前后缀的长度为0的时候
指针向右移动直到指针左边的长度为0(对比前后缀的长度不为0的时候,右移后指针左边的长度就是前后缀的长度)
所以当前后缀的长度为0时,移动后左边子串的长度为0
例如:子串第6号位的公共前后缀的长度(ABA),长度为3,所以就是第4号位与主串进行比较,next 数组所记录的数就是为 4 , p = 6 , next [ 6 ] = 4 , 接下来就是比对是 s[ 6 ] 和 t [ next [ 6 ] ] 的字符
图1 第6号位时
图2
图3
上面的规律是在 主串和子串 从下标为 1 开始的前提下
如果下标从 0开始,显而易见 next 数组的值就要 -1
那么 next [ i ] = j的含义为 ,下标为 i 的字符前的字符串最长相等前后缀的长度为 j
如图1 , 第6号位发生不同时 ( 第6号位 , 下标为5 ) , next [ 5 ] = 3
那么 ,总结一下,next数组的作用有两个
一是之前提到的,
next[i]的值表示下标为i的字符前的字符串最长相等前后缀的长度
二是:
表示该处字符不匹配时应该回溯到的字符的下标,即模式串需要回退的位置
next有这两个作用的源头是:之前提到的字符串的最长相等前后缀
例题
Acwing
代码实现
#include <iostream>
using namespace std;
const int N=10010, M=100010;
int n, m;
char p[N], s[M];
int ne[N];
int main()
{
cin >> n >> p+1 >> m >> s + 1;
//求next的过程
for(int i = 2,j = 0;i <= n;i ++)
{
while(j && p[i] != p[j +1]) j = ne[j];
if(p[i] == p[j+1]) j++ ;
ne[i] = j;
}
// kmp 匹配过程
for(int i = 1, j = 0;i <= m; i ++)
{
while(j && s[i] != p[j+1] ) j= ne[j];
if(s[i] == p[j+1]) j++ ;
if(j == n)
{
printf("%d ",i - n);
j = ne[j];
}
}
return 0;
}
多多手推,就能熟能生巧了~