To_Heart—总结——KMP

KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt提出的,因此人们称它为克努特—莫里斯—普拉特操作(简称KMP算法)。

——度娘


在一个古老的数字王国,每个物质的身份都可以用一组数字来表示,而这串数字就成为TA的身份号码,就比如我们的主人公小明,他的身份号码为5455408854087.

数字王国的居民也十分的喜欢购物,如果说一位居民购买了一个物品,那么这个物品的身份号码就一定是其主人的子串。举个栗子,小明今天去买了一本《算法竞赛》,那么这本《算法竞赛》的身份号码就会改变成54

因为小明买了一本《算法竞赛》,所以小明觉得自己可以AKIOI在信息竞赛方面很有天赋,于是便报名参加了FCC举办的J-PSC的比赛,准备拿下省一,AKNOI,进入集训队,再AKIOI,走上人生巅峰。

但小明在出赛场时发现自己的《算法竞赛》找不到了,于是去失物招领处寻找,但他发现失物招领处的《算法竞赛》有很多很多本。没办法,小明只好一本一本的查找身份号码,希望找到属于他的那一本。

小明首先用自己的每一位上的身份号码去比对每一本《算法竞赛》的身份号码的首位,如果相等则再逐位比较,否则直接跳到下一本《算法竞赛》再继续比对。

首先,小明先拿出了一本算法竞赛

这一本《算法竞赛》的身份号码为:5454

根据小明的原始比较方式,小明需要进行如下步骤:

第一步: 5和5比较,相同,于是比较下一位,

第二步,4和4比较,相同,于是比较下一位,

第三步,5和5比较,相同,于是比较下一位,

第四步,5和4比较,不同,于是只好重新比较。

……

第不知多少步,7和5比较,不同,所以这本书不是小明的。

但小明发现如果这么一本一本的比较下去,时间复杂度是本数乘以两个字符串的长度积 ,那么他就没有时间去AKIOI了,于是他发动他AKIOI的智慧,想对他寻找的方式进行优化;

灵机一动,小明想到了一个有关最长公共前后缀1的结论。

如果一个字符串a的后缀等于其最长公共前后缀,且这个后缀等于另一个字符串b,那么必存在a的一个前缀2,使得这个前缀等于字符串b。

那么这个结论有什么用呢?小明陷入了沉思。

假设现在小明拿了一本新的《算法竞赛》,它的身份号码为54088541;

让我们再来回顾一下小明的身份号码:5455408854087;

假设我们现在已经比较到了小明身份号码的第四位,即5;

小明用《算法竞赛》的身份号码进行比较:

第一次,5=5,比较下一位;

第二次,4=4,比较下一位;

第三次,0=0,比较下一位;

第四次,8=8,比较下一位;

……

第七次,4=4,比较下一位;

第八次,1和7不相等。

所以目前来看,这本《算法竞赛》不属于小明。

但小明注意到,目前来看,这两个身份号码通过我们的比较后有七位是相同的,即均含有5408854

我们再求一下这个公共子串的最长公共前后缀:

编号  	字符 	 最长公共前后缀
1 		5408854 	54

因为此时我们的后缀54已经和小明的身份号码的部分相等了,且54是公共子串的最长公共前后缀,所以可以将公共子串的一个相同前缀直接移动到后缀的位置上,再进行下一轮的比较。

具体操作流程如下:

目前状态:
5 4 5 5 4 0 8 8 5 4 0 8 7
	 (5 4)0 8 8[5 4]7
变换后:
5 4 5 5 4 0 8 8 5 4 0 8 7
		       (5 4)0 8 8[5 4]7

其中,“()”圈起来的为前缀“[]”圈起来的为后缀。

小明发现,下次的比较就可以直接从第11位开始,而不是又从第六位进行暴力运算。于是时间复杂度由O(nm)变为了O(n),其中n为小明身份号码的长度,m为《算法竞赛》的身份号码长度。

如果看到这里你没有一点疑问,那么请给自己一耳光

但细心的小明发现,我们求最长公共前后缀的时间复杂度也是O(m)级别的,所以我们的时间复杂度并没有减少。。。

那么有没有可能一次求出《算法竞赛》的身份号码的所有前缀的最长公共前后缀呢??

小明准备用一个next数组来存放每个前缀的最长公共前后缀。

那么怎么计算next数组3呢?

小明又发现了一个规律:组成最长公共前后缀的前缀的最后一位的下标刚好是最长公共前后缀的长度4

这个规律对求解有什么用呢?

小明决定定义一个字符串a和两个指针,i和j。

其中i表示当前遍历到的字符,j表示最长公共子串的长度,同时也是组成其的前缀的最后一位的下标。

则如果a[i]=a[j+1],那么next[i]=j+1;

如果他们两个不等,则将j变为next[j],即找j的最长公共前后缀,直到a[i]=a[j+1]或j=0。

小明还是迷迷糊糊的,可是上天被他感动了,给了他一个求next数组的代码,让他自己再参悟参悟

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

(正在施工)


  1. 最长公共前后缀,指一个字符串的前缀和后缀的最长公共部分的长度,举个例子,aba的前缀有aba,后缀也有aba,所以aba为aba的公共前后缀,又因为不可能有比aba还大的公共前后缀,所以aba是aba的最长公共前后缀; ↩︎

  2. 在这里,我们规定,前缀不算首字符,下文均按照此规定; ↩︎

  3. 在这里,所有的下标均由1开始存储; ↩︎

  4. 举个例子,abca的最长公共前后缀的长度为1,所以组成其的前缀字符串的最后一位下标为1. ↩︎

猜你喜欢

转载自blog.csdn.net/xf2056188203/article/details/109673221