图解KMP算法

模式串匹配--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;	
}

多多手推,就能熟能生巧了~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值