KMP算法

kmp算法用于快速模式串匹配。
首先我们先来暴力解一下关于这道题的模式串匹配问题:
在这里插入图片描述

#include<iostream>
using namespace std;

const int N = 100010, M = 1000010;

int main() {

	int i, j;
	int n, m;
	char p[N], s[M];

	//我们让子串和主串的下标从1开始
	cin >> n >> p + 1 >> m >> s + 1;

	for (i = 1; i <= m - n + 1; i++) {
		for (j = 1; j <= n; j++)
			if (s[i + j - 1] != p[j]) 
				break;
		if (j == n + 1)
			cout<<i-1<<' ';
	} 
}

一个二重循环不断回溯的过程,暴力解法的时间复杂度为(m*n)
下面我们来了解用kmp算法解该题。
首先来看个例子:
6
abcabg
12
abcabeabcabg
在这里插入图片描述
图中匹配到最后一个g就是蓝色部分,发现匹配失败了
如果按照暴力做法的话,那么子串中的指向要从g变为a,主串中指向要从e变为b
而按照kmp算法,主串中不用进行回溯,只需要让子串中的元素回溯到g以前某个字符进行再比较。至于回溯到g以前具体哪个字符,则需要用到我们接下来要讲的next数组。next数组如何构成,也是kmp中最复杂的部分。
讲KMP之前我们先来认识一下,什么叫最长相等前后缀,就拿我们上面截图里的子串中的前五个元素abcab举个例子吧。前缀有:a,ab,abc,abca等。后缀有:b,ab,cab,bcab。那这里的最长相等前后缀等于什么呢?嗯,就是,ab。
在这里插入图片描述

如上图,第三排是我们第二次子串和主串可以匹配上第一个元素的配对。而子串中黑色的c就是我们g要回溯的地方。发现什么规律了吗?很明显,如果能配对上,那么D5=D4=D2。又因为D5是D3通过平移来的,所以D3=D5,D3=D4。我们要回溯的部分其实就是D3的后一个元素。D3(子串ab)又是什么呢,是包括b及b之前的最长前缀。next存的就是这个包括这个元素及该元素之前与元素的最长相等前缀的长度,比如这里的next[5]=2;
好,根据上面的信息,我们能轻松得到abcabg的next数组的相应信息:
在这里插入图片描述

那我们来看看如何用代码实现求next数组吧:

for(int i=2,j=0;i<=n;i++){
	while(j&&p[i]!=p[j+1])j=next[j];
	if(p[i]==p[j+1])j++;
	next[i]=j; 
}

可以用一重for循环直接求得next数组。
下面我们来详细了解一下这段代码的具体含义吧:
在这里插入图片描述

如果p[i]=p[j+1],那么i及i前的元素最长相等前后缀长度增加,蓝色部分变为红色部分。
在这里插入图片描述
如果p[i]!=p[j+1],那么j=next[j];next[i]及next[j]后的元素和i后灰色的部分就构成了最长相等前后缀。如果j=0的话,那么让j=next[j]会陷入死循环,当j=0时,相当于没有最长相等前后缀,所以前缀从头又开始找。
了解了如何创建计算next数组后,我们来看看如何通过next数组来进行KMP的主串与子串的匹配。
代码如下:

for(int i=1,j=0;i<=m;i++){
	while(j&&s[i]!=p[j+1])j=next[j];
	if(s[i]==p[j+1])j++;
	if(j==n){
		cout<<i-n<<' ';
		j=next[j];
	}
}

下面让我们来分析一下这个代码:
其实字符主串与子串之间的匹配与之前的找next数组有点类似
在这里插入图片描述
如果s[i]和p[j+1]相等的话,那么这里的绿色区域会变为匹配成功的蓝色区域,当然,那个时候对应红色区域也会发生一定的变化,前后缀长度就会变成next[j+1]的长度,如果s[i]!=p[j+1]的话,那么j=next[j]的区域,相当于next[i]之前的字符还是可以与i前面的红色部分匹配的。这是j=0的情况与上面求next数组的理解类似。那个if(j==n)就相当于已经匹配完成了,此时i的地址相当于j+n的地址,所以要返回匹配完成时的初始地址,就要打印i-n。因为我们的题中可能出现不止一次的匹配成功的情况,所以打印一次之后还要进行后续操作,让j=next[j],我相信经过上述的讲解,和依据下图,你们能明白这行代码的作用。
在这里插入图片描述
下面是完整代码:

#include<iostream>
using namespace std;

const int N = 100010, M = 1000010;

int n, m;
int next[N];
char p[N], s[M];

int main() {
	cin >> n >> p + 1 >> m >> s + 1;
	for(int i=2,j=0;i<=n;i++){
		while(j&&p[i]!=p[j+1])j=next[j];
		if(p[i]==p[j+1])j++;
		next[i]=j; 
	}
	for(int i=1,j=0;i<=m;i++){
		while(j&&s[i]!=p[j+1])j=next[j];
		if(s[i]==p[j+1])j++;
		if(j==n){
			cout<<i-n<<' ';
			j=next[j];
		}
	}
	return 0;
}

在VS中全局变量命名next可能会报错,可以用ne来表示。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值