KMP算法

我们经常会遇到一些字符串匹配的问题,例如,寻找一个字符串在另一个字符串里的位置。我们把给定两个串S=“s1s2s3 …sn”和T=“t1t2t3 …tn”,在主串S中寻找子串T的过程叫做模式匹配,T称为模式。

朴素的字符串匹配

朴素的做法是从S和T的开始位置0开始,依次进行匹配判断,直到达到T的结尾或者匹配失败:

匹配失败时,回到T的开始位置,并将T右移一位,和S的下标1对齐,再重复前面的操作。

素朴的暴力算法存在一个S回退的问题,明明我们已经匹配到下标为3的位置,下一次又要回到下标为1的地方从头开始匹配。

KMP算法

KMP算法一种改进的模式匹配算法,是D.E.Knuth、V.R.Pratt、J.H.Morris于1977年联合发表,KMP算法又称克努特-莫里斯-普拉特操作。它的改进在于:每当从某个起始位置开始一趟比较后,在匹配过程中出现失配,不回溯i,而是利用已经得到的部分匹配结果,将一种假想的位置定位“指针”在模式上向右滑动尽可能远的一段距离到某个位置后,继续按规则进行下一次的比较。

最大公共前后缀

我们假设一个长度为n的字符串的前缀为从下标为0开始,长度为1到n-1之间的子串。后缀也是类似的。公共前后缀为相同的前后缀。现在有串P=abaabca,各个子串的最大公共前后缀长度如下表所示:

从上表,我们发现每当子串长度加1的时候,子串会增加一个前缀,而会在每个后缀后面增加一个新的字符(假设存在一个空后缀,所以会增加一个新字符的后缀)。当子串为abaa的时候,公共前后缀最大长度为1。当子串为abaab的时候,我们只要判断新增的字符b是否跟前缀a后面一个字符是否相等,相等则公共前缀最长长度就加1,否则就缩短公共前缀进行判断。

缩短公共前缀后必须仍然有公共前缀,因此我们这里要取公共前缀的最大公共前缀。然后我们再重复前面的操作。

假设我们在pk 和 pj处失配,我们可以看到,在pj之前,有一段长度为k的已匹配串;在pk之前有一段蓝色的已匹配串,它是当前已匹配串的最大公共前后缀(蓝色的那段)。

next数组

现在我们引入next数组,next 数组的值是除当前字符外(注意不包括当前字符)的公共前后缀最长长度,相当于把上表做一个变形,将表中公共前后缀最长长度全部右移一位,第一个值赋为-1。例如c对应next值的意义是,c之前(不包括c)的子串abaab所拥有的公共前后缀最长长度为2,我们称next数组中的值为失效函数值,也就是c的失效函数值为2。next[i]表示的是当前位置i失配时,T串应该移动到的下个位置。

void GetNext(char T[])
{
    //j是当前字符串下标,k是最长公共前缀长度
	int j=1,k=0;
	next[0]=-1;
    next[1]=0;
	while(T[j]!='\0')
	{
		if(T[j]==T[k])
		{
			next[++j]=++k;
		}
		else k=next[k];
	}
}

算法实现

当S串和T串在当前位置匹配时,就移动到各自的下个位置。

如果失配,则将T串移动到next[j]和S串的当前位置对齐,再重复前面的操作。

int KMP(int start,char S[],char T[])
{
	int i=start,j=0;
	while(S[i]!='\0'&&T[j]!='\0')
	{
		if(j==-1||S[i]==T[j])
		{
			i++;         //继续对下一个字符比较 
			j++;         //模式串向右滑动 
		}
		else j=next[j];
	}
	if(T[j]=='\0') return (i-j);    //匹配成功返回下标 
	else return -1;                 //匹配失败返回-1 
}

参考

KMP算法详解-CSDN博客

什么是KMP算法(详解)-CSDN博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值