KMP算法理解

next放比赛上会编译错误,可能是关键字,所以不要用next

目录

第一个问题,我们为什么要找对称的子串

第二个问题,now不是P[0]~P[x-1] 中最长的公共前后缀怎么办

模板


https://www.zhihu.com/question/21923021

上文是原理,下面是我阅读该文产生的一些问题并做出解答

第一个问题,我们为什么要找对称的子串

解答:


从结论上看:
如果在第i位失配
那么就要找next[i-1] 也就是失配的前一位开始画标尺
因为正常来说,模式串P至少要后退1位
所以从i-1开始找
next[i]=k
k的意义是在P[0,i]中,P[0,k-1]=P[i+1-k,i] 也就是说把P从i处截断,前k位组成的子串和倒数前k位组成的子串是相等的
注意这个相等不代表关于某个字符对称,长度为偶数的话是关于中间两个字符对称



那么问题来了,我们为什么要找对称的子串?


下面我们从头开始推导算法
如果我们在第i+1位失配,那么就代表前i位P[0,i]和主串的往前数i+1位S[j-i,j]是相等的,那么问题就可以转化了
转化成什么呢?原先我们要求的是怎样尽可能少的移位,现在我们意识到了对S进行移位和对P进行移位是等价的,因为他们前几位组成的字串相等,那我们研究P不可以了,为什么还要看S呢?换句话说,每次失配后移动的位置仅仅与P有关,与主串S是无关的,这是固属于P的性质。
great!现在我们来研究P:
abaabac在c处失配,abaaba与失配前的主串相同,整个abaaba肯定不能继续匹配了,我们要把这个字串砍去一部分才能继续匹配。假设我们从头开始参与匹配,那就是暴力了


设主串为abaabax,x为任意不同于c的字符串,那么 

abaabax        -> abaabax     ->abaabax    ->abaabax
abaabac        ->   abaabac    ->   abaabac    ->  abaabac


我们发现,从第一步到最后一步,中间的步骤全部无用功,如果我们能找到一种办法跳过中间暴力的无用功,那么算法的时间复杂度将大大降低
可以看到,x之前被匹配的是aba,如果x=a,那么还能继续匹配。判断x是否等于a之后的处理我们先不考虑,我们发现我们跳过的部分和我们匹配到的部分是一模一样的!
也就是说,如果我们在第i+1位失配,那么我们要在前i+1位P[0,i]寻找到前面等于后面的子串,
再举个例子说明等于或者说对称的意义:

abertzqabx x为任意字符且在此处失配
abertzqabx 
            abert……


也就是说,找到这个等于并且最好是最大对称串,然后找到子串末尾+1的下标,用这个下标的字符与x进行匹配就可以了,因为前面肯定是可以匹配上的。
设这个下标为k,那么我们自然提出结论:k的意义是P[0,i],P[0,k-1]=P[i+1-k,i] 也就是说把P从i处截断,前k位组成的子串和倒数前k位组成的子串是相等的
问题解决
如果你觉得这不是数学推导,不够严谨,没关系,我们来一波逻辑论证。
设失配处为x,则主串可以表示为ABCx,其中A、C是相等的字符串(不是单个字符),B是与A、C不相等的字符串,他们具有任意性。

下面模拟S和P失配的情景 y表示任意与x不相等的字符
ABCx    ->ABAx    ->ABAx        
ABCy    ->ABAy    ->  ABAy


上面我们把C代换成A,然后由于B与A不等,所以A和B肯定无法匹配,所以我们直接跳过B,那么就来到A与A匹配的环节,下面只需要判断B的第一个字符与x是否匹配即可
然后问题就转化为如何寻找对称子串A    推导完毕

第二个问题,now不是P[0]~P[x-1] 中最长的公共前后缀怎么办

原文:如图。长度为 now 的子串 A 和子串 B 是 P[0]~P[x-1] 中最长的公共前后缀。可惜 A 右边的字符和 B 右边的那个字符不相等,next[x]不能改成 now+1 了。因此,我们应该缩短这个now,把它改成小一点的值,再来试试 P[x] 是否等于 P[now].

那要不是最长的呢?其实也是一样的 比如说你把失配位之设置为子串B abcab 的B[1] b上,他可不是最长的,但是按照算法照样拓展。因为前后缀是不可能从中间开始截取的,不然不符合前后缀的定义(你从中间截取符合前缀不符合后缀)。所以是不是最长大可不必去判断,不是又如何?你还能从中间开始拓展么。

模板

#include<stdio.h>//https://www.zhihu.com/question/21923021
#include<string.h>
const int N=1e7+10;
char S[N],P[N];//主串S,模板串P 
int nex[N],ans[N],cnt;//ans存放答案,cnt为ans下标  注意:next是关键字,比赛会报错编译失败! 
void getNext(const char P[],int nex[]) {
	int q,k;
	int m = strlen(P);
	nex[0] = 0;
	for (q = 1,k = 0; q < m; q++) {//P从q处截断 
		while(k > 0 && P[q] != P[k])
			k = nex[k-1];//当P[now]与P[x]不相等的时候,我们需要缩小now——把now变成nex[now-1],直到P[now]=P[x]为止
		if (P[q] == P[k]) {
			k++;
		}
		nex[q] = k;
	}
}
void kmp(const char S[],const char P[],int nex[]) {
	int n,m;
	int i,q;
	n = strlen(S);
	m = strlen(P);
	getNext(P,nex);
	for (i = 0,q = 0; i < n; i++) {//i为主串S的下标,q为模串P的下标 
		while(q > 0 && P[q] != S[i])//匹配失败 失败处为q 那么找q-1的k 
			q = nex[q-1];//逻辑同12行 
		if (S[i] == P[q]) {//当前字符匹配成功 
			q++;
		}
		if (q == m) {//子串匹配成功,加入答案 i为主串中模板终点,现在要找起点x,那么根据区间长度有i-x+1=m,则x=i-m+1 
			ans[cnt++]=i-m+1;
		}
	}
}
int main() {
	gets(S),gets(P);
	getNext(P,nex);
	kmp(S,P,nex);
	for(int i=0;i<cnt;i++)	printf("%d ",ans[i]);
	return 0;
}

例题

https://acm.hdu.edu.cn/showproblem.php?pid=1686

#include<stdio.h>
#include<string.h>
const int N=1e7+10;
char S[N],P[N];//主串S,模板串P
int next[N],cnt,n,m;//ans存放答案,cnt为ans下标
void init() {
	cnt=0;
}
void getNext(const char P[],int next[]) {
	int q,k;
	m = strlen(P);
	next[0] = 0;
	for (q = 1,k = 0; q < m; q++) {//P从q处截断
		while(k > 0 && P[q] != P[k])
			k = next[k-1];
		if (P[q] == P[k]) {
			k++;
		}
		next[q] = k;
	}
}
void kmp(const char S[],const char P[],int next[]) {
	int i,q;
	n = strlen(S);
	m = strlen(P);
	getNext(P,next);
	for (i = 0,q = 0; i < n; i++) {//i为主串S的下标,q为模串P的下标
		while(q > 0 && P[q] != S[i])//匹配失败 失败处为q 那么找q-1的k
			q = next[q-1];
		if (S[i] == P[q]) {//当前字符匹配成功
			q++;
		}
		if (q == m) {//子串匹配成功,加入答案 i为主串中模板终点,现在要找起点x,那么根据区间长度有i-x+1=m,则x=i-m+1
			cnt++;
		}
	}
}
int main() {
	int z;
	scanf("%d",&z);
	getchar();
	while(z--) {
		gets(P),gets(S);
		getNext(P,next);
		kmp(S,P,next);
		printf("%d\n",cnt);
		init();
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值