KMP算法

这个算法研究了一天,从理论推导到C语言实现,作为初学者,还是不太容易,尤其很多资料对于next矩阵有的从0开始有的从1开始。作为初学者尤其是在具体实现是可能会搞混,但其实其本质是一样,首先需要理解算法的本质,然后再考虑形式上差异就会比较容易。
首先简单介绍朴素的模式匹配算法,然后详细介绍KMP算法。

朴素的模式匹配算法

KMP算法_第1张图片

KMP算法_第2张图片
从两张图可以看出,朴素的匹配算法是在发现不完全匹配后,匹配字符串向后平移一个单位,继续匹配;不难发下i值在第一次匹配中走到3;发现不等,第二次匹配又从新从1开始;也就是说i值发生了回溯。
对于本图中的匹配字符串可以发现,ababcx中存在重复的字符;所以在第一次匹配发现i=3与j=3处的值不相等,此时由于j=0处的值和j=1处的值不相等,但是j=1处的值与i=1处的值相等,所以j=0与i=1处的值一定不相等,所以第二次的匹配是多余的,为了进一步提高匹配效率,伟大的前人提出了KMP算法。

KMP算法

从上图中可以看出,有效的匹配应该是从j=0与i=2处对其开始匹配,但是由于j=0与j=2处的值是相等的,所以该位不用匹配,从j=1,i=3开始匹配,如图

KMP算法_第3张图片
可以发现,这种匹配方法中i值并没有发生回溯,当然了j值发生了回溯;所以KMP算法其实是一种j值回溯的算法。
那么如何确定j值该回溯到什么位置呢?这就是很多资料中提到的next数组的求取。
之所以j值回溯是因为匹配字符串中存在相同的字符,倘若匹配字符串中没有相同的字符,其实KMP算法与朴素匹配算法相同,因为KMP算法的j值回溯也会回溯到0。
所以next数组的求取,其实是对匹配字符串的相似度的求取;
相似度求取方法如下:
以ababcx为例
这里写图片描述
关于相似度的求取有必要介绍一下前后缀;
前后缀都是在当前字符之前的字符串中的;
例如c,前后缀是在abab中找的,前缀有aba,ab,a;后缀有bab,ba,b;
其实相似度指的就是前后缀相似度;
j=0时:a不存在前后缀,相似度为0;
j=1时:b的前缀为a;不存在后缀;相似度为0;
j=2时:a的前缀为a;后缀为b;相似度为0;
j=3时:当b的前缀为ab时后缀为ba相似度为0;
当b的前缀为a是,后缀为a;相似度为1;
j=4时:c的前缀:aba对应后缀bab相似度为0;
c的前缀:ab对应后缀ab相似度为2;
c的前缀:a对应后缀b相似度为0;
j=5时:前缀:abab 后缀:babc
aba abc
ab bc
a c
相似度是指前后缀中相同字符串的最大长度;所以j=4相似度为2;
j=5相似度为0;
再举个例子:abcabx
这里写图片描述

再举个复杂的例子:
ababaaaba

KMP算法_第4张图片

以上是关于next数组的求法。但是不同的资料可能求得的结果不同,原因在与j值不同,文章开头提到的有的资料j从0开始取,有的资料j从1开始取;
本文j从0开始取;这样有利于C语言实现;因为数组一般会从0开始。接下来介绍一下二者的差别,以及二者如何相互转化。

j从0开始与从1开始的差别以及二者的转化

首先需要考虑next数组的本质,next数组是为了在i与j处字符不同时,用来确定j回溯到什么位置的。
举个例子:
这是j从0开始的示例:
KMP算法_第5张图片

KMP算法_第6张图片
从两图中可以看出,b(j=3)处不相等了,此时回溯,next[3]=1;所以j回溯到1;
倘若j是从1开始的,那么他的相似度与上文介绍的有差异;
看图:
KMP算法_第7张图片
KMP算法_第8张图片
容易看出这样j回溯到的位置应该为2而非1;
这样看来差异只不过是标号的问题,但是回溯都是回溯到b;其实是这样的,但是在有些资料中可以看到,即使是j从1开始,但是并不是把j从0开始的next数组元素加一得到;而是j=1时,next[j]=0;其他位置由j从0开始的next数组元素加1得到。我理解是为了代码实现的方便,让next[1]=0,但是具体就不知道是不是这样了,欢迎大家留言讨论;个人习惯使用从0开始,从1开始各种处理比较复杂。
本问主要介绍算法的理论部分,代码部分会在下一篇博文中介绍;主要实现朴素模式匹配和KMP模式匹配两种算法,比较其差异。

你可能感兴趣的:(C语言数据结构相关实验)