KMP(1)-前缀函数(Prefix function)KMP的next函数

前言:

   前段时间学习和字符串相关的算法,自然学到了KMP算法(解决单字符串匹配问题)。这里简单记录一下,方便以后回忆。

  先给出二个名词,模式串(P), 文本串(T)。 比如ABC是否是ASDABC的子串,这里ABC是模式串(P),ASDABC是文本串,也叫被匹配串。

引入:

  KMP算法其实就是两个部分,①:前缀函数(求前缀数组)②:匹配部分。

   那么什么是前缀函数呢??这是本篇文章的内容,为什么需要这个前缀函数这里先不解释,下篇文章讨论。

前缀函数:

  前缀函数的功能是求出”模式串“的next[ ]数组。
  首先要了解三个概念,前缀、后缀、匹配值。
  ①本算法中的"前缀"和"后缀": "前缀"指除了最后一个字符以外,一个字符串的全部头部组合;"后缀"指除了第一个字符以外,一个字符串的全部尾部组合。
  //比如ababa的前缀a,ab,aba,abab (注意没有ababa) 后缀有b,ba,aba,baba(注意没有ababa) 

  ②匹配值:就是字符串中"前缀"和"后缀"的最长的共有元素的长度。对于ababa来说就是3,说明ababa的“前3个字符”与“后3个字符”相同aba.(PS:这里我们也可以看出为什么前缀不包括最后一个字符,后缀不包括第一个字符了,因为包括的话就没意义了。。ababa前5个字符等于后5个字符,匹配值等于它长度就好了。。)

  那么next[]数组用一句话概括就是“若模式串P的前i个字符串组成的子串为S,那么next[i]等于S的匹配值。
  拿ababa来说.next[5]=3,这说明前5个字符组成的字符串ababa,前3个字符与后3个字符相同均为aba.
 同样我们可以算出next[4]=2,next[3]=1,next[2]=0,next[1]=0.("a"的前缀和后缀都为空集,共有元素的长度为0;)
再举一例aaaaa,next[5]=4,next[4]=3,next[3]=2,next[2]=1,next[1]=0(加深理解)

代码:

前缀函数是KMP思想的精髓,其代码实现仅有短短13行(算法导论上的标准写法)
//p是模拟串
//字符串都是从下标1开始的。
void getNext(char p[])  {
	int m = strlen(p + 1);
	next[1] = 0;
	for (int k = 0, q = 2; q <= m; q++) {
		while (k > 0 && p[k + 1] != p[q]) k = next[k]; //不理解没关系看下面的分析
		if (p[k + 1] == p[q]) k++;
		next[q] = k;
	}
}
现在我着重讲解一下while循环所做的工作
比如算ababac.我们求next[6]的时候,已经算出next[1] next[2] …next[5]的值了。
此时k=next[5]=3.说明p[1]p[2]p[3]=p[3]p[4]p[5]
我们发现p[4]和p[6]失配说明,p[1]p[2]p[3]p[4]!=p[3]p[4]p[5]p[6].失败了难道要重新来过?不存在的。
p[1]p[2]p[3] = p[3]p[4]p[5]。你会想如果p[1]p[2]=p[2]p[3].那就有p[1]p[2]=p[2]p[3]=p[4]p[5]啦。
自然的想到,那要是p[3]=p[6].那就成了。next[6]=3。
但是不存在p[1]p[2]=p[2]p[3]=p[4]p[5]的。因为next[3]=1而不是2。只有p[1]=p[3]=p[5]..那么我们下一步判断的是p[2]是否等于p[6].
即k=next[k].判断p[k+1]是否等于p[q]。若不相等继续循环。(想明白这个,这段代码就搞定了)
PS:好好想想就知道了。 我们需要 找P[1]打头、P[k]结尾的子串即P[1]···P[j]( j==next[k] ),看看它的下一项P[j+1]是否能和P[q]匹配

练习:

1.[POJ][2406] Power Strings.
2.题目描述
给出一个字符串,问其是由多少个相同的子串组成.比如abcabc就是2,abc就是1.
3.分析
 考验对next[]数组的理解。由next[i]的理论可以推 知。若此字符串有最小循环节,那么循环次数一定为len/(len-next[len])。
len(p)-next[len(p)]是模式串P的”循环节“长度。
简单说下原理:


假设图中的黑色是原来的字符串,现在要求最小循环节,对于next[len]来说指的是图中黄色和蓝色长度,而且黄色和蓝色是相等的,

那么绿色和紫色也是相等的,对比原串可知紫色跟粉色是相同的子串,那么绿色跟粉色相同,然后对比蓝色跟黄色可知红色跟粉色相同,对比原串,红色跟棕色相同,那么棕色跟粉色相同,不断重复此过程可知若此字符串有最小循环节,那么循环次数一定为len/(len-next[len])。

4.code

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
#define SIZE 1000000
int nex[SIZE+5];//如果你有用到std的话,就不要定义next,否则会命名冲突. 
char p[SIZE+5]; 
void prefix(char p[])
{
	int m=strlen(p+1);
	nex[1]=0;
	for(int k=0,q=2;q<=m;q++)
	{
		while(k>0 && p[k+1]!=p[q])
			k=nex[k];
		if(p[k+1]==p[q])
			k++;
		nex[q]=k;
	}
}
int main()
{
	while(scanf("%s",p+1) && strcmp(".",p+1))
	{
		prefix(p);
		int len=strlen(p+1),cycle=len-nex[len];
		printf("%d\n",len%cycle?1:len/cycle);
	}
	return 0;
} 


  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值