KMP算法

KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt提出的,因此人们称它为克努特—莫里斯—普拉特操作(简称KMP算法)。KMP算法的核心是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是通过一个next()函数实现,函数本身包含了模式串的局部匹配信息。KMP算法的时间复杂度O(m+n) ---- 百度百科

eg.看这个之前可以先试试BF算法再来

区别:KMP和BF唯一不一样的地方就是,主串的i不会回退,而子串的j也不一定会移动到0号位置,而这同时也是KMP算法的目的

next数组

用于保存字串在某个位置匹配失败后,回退后的位置
每一个字符都需要去求出当前位置的回退位置

  • KMP算法的精髓就是next数组,也就是用next[j]=k来表示,不同的j对应一个不同的k值,这个k就是将来j要移动的位置
  • 而k的值是这样求的:
    1.规则:找到匹配成功部分的两个相等的真子串(不包含本身),一个以下标0开始,另一个以j-1下标结尾
    2.不管什么数据,next[0]=-1,next[1]=0

举一个例子:abcabc

j        0 1 2 3 4 5
         a b c a b c
next[j] -1 0 0 0 1 2

这是怎么算出来的呢?
我们看规则,找到匹配成功部分的两个相等的真子串,一个以下标0开始,另一个以j-1下标结尾

首先我们知道,next[0]=-1,next[1]=0,所以前两个不用管,而当下标j走到c时,下标j-1指向的就是b,而前面没有出现过b,所以next[2]是0,而next[3]也是一样的,因为c也是第一次出现,不会找到两个c。而next[4],j-1指向a,以a开头以a结尾,只能发现两个相等的串"a",长度为1,next[4]=1;而next[5],j-1指向b,以a开头以b结尾,发现两个相等的串"ab",长度为2,故next[5]=2

两个小练习:

a b a b c a b c d a b c d e
a b c a b c a b c a b c d a b c d e

第一个:

j        0 1 2 3 4 5 6 7 8 9 10 11 12 13
         a b a b c a b c d a b  c  d  e
next[j] -1 0 0 1 2 0 1 2 0 0 1  2  0  0  

第二个:

j        0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
         a b c a b c a b c a  b  c  d  a  b  c  d  e
next[j] -1 0 0 0 1 2 3 4 5 6  7  8  9  0  1  2  3  0

第一个比较简单,那我们就讲第二个
首先还是-1,0;j=2:然后c的前面是b,第一次出现所以为0;j=3:a的前面是c,也是第一次出现,为0;j=4:以a开头以a结尾,所以找到“a”,长度为1;j=5:以a开头以b结尾,“ab”,长度为2;j=6:以a开头以c结尾,“abc”,长度为3;j=7:以a开头以a结尾,“abca”,长度为4;j=8:以a开头以b结尾,“abcab”,长度为5;j=9:以a开头以c结尾,“abcabc”,长度为6…后面的也都是一样的

而且在计算的过程中我们会发现几个点:
①只要出现新的字符,那么这个字符的下一个字符的next[j]一定为0
②next[j]都是逐个加起来的,不会说直接从1蹦到3、4或者更大的数,而是1,2,3…

而接下来的问题就是:已知next[i]=k,如何求next[i+1]?

首先假设next[i]=k成立,那么P0…Pk-1=Px…Pi-1,由此得到:P0…Pk-1=Pi-k…Pi-1。

k-1-0=i-1-x  ->  x=i-k;

我们再假设:如果Pk=Pi,那么我们就可以得到P0…Pk=Pi-k…Pi,那么这个就是next[i+1]=k+1

举例

j        0 1 2 3 4 5 6 7 8 9 10
         a b c a b a b c a b c
next[j] -1 0 0 0 1 2 1 2 3 4 5

当j=8时,前提:假设next[i]=k,P[0] – P[k-1]=P[i-k] – P[j-1],而在这里是P[0] – P[2],P[5] – P[7]是相等的。
现在我们还会发现,其实P[8]==P[3],也就是P[i]==p[k],所以加上这个条件:P[0] – P[k]=P[i-k] – P[j],而这就可以推出next[i+1]=k+1。而发生这个需要两个前提:①next[i]=k②P[i]=P[k]

那么有P[i]=P[k],就会有P[i]!=P[k]的情况,那这时next[i+1]=?

j        0 1 2 3 4 5 6 7 8 9 10
         a b c a b a b c a b c
next[j] -1 0 0 0 1 2 1 2 3 4 5

还是用这个串来举例,我们先让j=5,那么k就要回退到2号位置,因为next[5]=2,而此时我们会发现k=2时next[2]=0,而此时j又回退到0号位,此时next[5]=next[0]=a,而此时k=0,next[i+1]=k+1,所以next[6]=0+1=1。

刚才是P[0]=a,所以我们找到了,如果此时P[0]!=a 呢?那么k就会继续回退等于-1,我们知道数组的序号不可能为负数,所以当k=-1时,此时也是next[i+1]=k+1,也就是next[i+1]=0

下面请看代码:

void GetNext(char* sub,int* next,int lenSub) {
	next[0] = -1;
	next[1] = 0;
	int i = 2;//当前i下标
	int k = 0;//前一项的k值

	//这里为什么使用i-1呢,因为
	while(i<lenSub)
	{
		if (k==-1||sub[i - 1] == sub[k])//k==-1如果放进数组就越界了,但是我们知道此时i指向的数据为0
		{
			next[i] = k + 1;//如果这里用next[i+1],那next[2]就算不出来了,所以把i==k改成了i-1==k
			i++;
			k++;//因为两个元素相等,所以都往后++
		}

		else
			k = next[k];//k回退到当前位置
	}
}

//KMP算法
int KMP(char* str, char* sub, int pos) {
	assert(str && sub);
	int lenStr = strlen(str);
	int lenSub = strlen(sub);

	if (lenStr == 0 || lenSub == 0)//如果两个中有空串的话
		return -1;
	if (pos >= lenStr || pos < 0)//如果一开始pos给的不正确的话
		return -1;

	int* next = (int*)malloc(sizeof(int) * lenSub);//创建next数组
	assert(next);

	GetNext(sub,next,lenSub);//next数组赋值

	int i = pos;//遍历主串
	int j = 0;//遍历子串
	while (i < lenStr && j < lenSub)
	{
		if (j == -1 || str[i] == sub[j])//j=-1时由于sub[j]会越界
		{
			i++;
			j++;//匹配成功,或者j==-1
		}

		else
			j = next[j];//j回退

		if (j >= lenSub)//找到了
			return i - j;
	}
	return -1;//没找到
}

以上就是我对KMP算法的了解,可能还有很多不太清楚的地方,但也希望大家看完之后能够有一个大概的了解。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值