KMP算法详解

        刚刚在微易码学习了KMP算法,深深的被这里面的逻辑吸引了,也不得不佩服微易码的主讲以及颜值担当“铁血教主”(他自己这么称呼自己鄙视)的缜密的逻辑性,让我的思路一直保持在很清晰的状态,才能较为轻松的基本掌握了这个算法的实现原理。为了以后不至于忘记里面的逻辑和自己的一些感悟,所以把KMP算法的实现较为系统的做一次分析,有说的不合理的地方还请多多指正。


        KMP算法是一个时间复杂度极为优秀的串匹配算法,它是由D.E.Knuth,J.H.Morris和V.R.Pratt三个人发现的。

        我们可以把KMP算法比作一匹好马,因为它有两个优秀的品质:1、好马不吃回头草; 2、每次向前行走都是有目标的走,不会走一步冤枉路。想要掌握KMP算法,一定要铭记这两个特点。

        众所周知,所谓串匹配算法就是在一个源字符串中找到想要找到的目标字符串。如果只是单纯的考虑能否实现,我们很容易完成该算法。只要用一个双重循环,用一层循环控制遍历整个源字符串,用第二层循环控制遍历子字符串,当找到了返回当前子字符串相对于源字符串的位置。若当前情况下不匹配,则子串和源串的下一个位置进行比较。由于该算法比较简单,这里不做过多的介绍,仅对其时间复杂度进行分析。

        该算法把整个源串遍历一遍,这是毋庸置疑的,无论什么算法都涉及到遍历整个源串,但是由于每次都要和整个子串进行比较,因此时间复杂度为(m * n)。该算法的手工过程如图所示:

        从图中可以看出,该算法存在着大量的控制源字符串的下标的往回移动,比如源字符串比较到下标为6的时候,由于在下标为7的位置失配,则,不得不将下标从7移动到2继续进行下一次比较,在对一个长度很长的字符串进行遍历的时候,这样的不必要的移动大量存在。因此该算法无法应用到更加大型的字符串差找中去。因此我们就要想办法减少这样的移动,KMP算法的精髓就在于能够不进行这样的移动,并且要做到每一次移动都是有的放矢,移动到应该移动到的地方去。下面我们就开始介绍KMP算法。

        我们在让子串跟源串的一部分进行比较时,当遇到不匹配的字符了,自然说明源串的这一段字符串跟我们要找的目标串不匹配,我们自然要移动目标串,使它跟下一段比较,这时,普通算法的做法是同时改变控制源串和子串的下标进行下一次比较,而KMP算法则保持源串的当前下标不变,只移动子串到相应的位置进行下一次的比较。那么,究竟移动多少,通过什么得到应该移动多少都是KMP算法的一部分,我们来观察这样一组字符串:


源串:   abababaaaabbbabab

目标串:abababb


        我们观察到,该目标串在下标为6的位置失配,那么则说明前六个位置的字符都是匹配的,我们想直接在适配的位置进行下一次比较。这是,该串截止到失配的位置都已经比较过了,而失配字符的后面还没有比较过,而此时本次比较已经结束了。而此时,我们就可以得到截至失配字符,前面的字符都是匹配的结论。即:“ababab”;这时如果将目标串向后移动一个单位,两者的对应关系就为:


源串:    abababa ... ...

目标串:  ababab ... ...


        此时,我们明显地看出前面的字符串都没有匹配,因此,后面的比较都是没有意义的。而我们向后移动两个单位的时候对应关系为:


源串:    abababa ... ...

目标串:    ababa ... ...


        因此,我们得到如果在目标串下标为7的位置失配,则应该将目标串向后移动两个位置进行下一次的比较的结论。以此类推,每一个位置都应该有一个失配后应该移动多少个单位的值。如果得到了这样的一组值,我们就能使每一次失配后的移动量达到最小。

        下面我们的工作就是得到每一个位置如果失配应该向后移动多少个单位的这样一组数据。我们首先介绍这个算法的手工过程:

        KMP算法的实现方案是首先给出一个数组,我们先叫它“nextLocal”,元素个数为目标字符串的长度。每一个元素的值“n”代表当以该元素下标为目标字符串的下标的字符失配时,应该让目标字符串的以“n”为下标的元素继续与源字符串失配的位置进行比较。该数组的实现过程如下:

        首先,如果在第一个元素就失配了,那么肯定要用第一个元素继续与源字符串失配的位置进行比较。因此,这个数组的第一个元素一定是“0”,这一点是容易想到的。之后的做法是:将目标字符串当前元素与nextLocal前一个元素的值作为目标字符串下标的元素进行比较,若相等,则将当前nextLocal的元素为上一个元素的值+1。若不相等,又分为两种情况:1、如果nextLocal数组当前元素的前一个元素的值为零,那么直接将当前元素赋值为0;2、如果nextLocal数组当前元素的前一个元素不为零,那么将当前位置的字符与目标字符串的arrayLocal数组中上一个位置的元素的值为下标的元素作比较,继续执行上述判断操作。具体代码如下:

void getNextLocalArray(char *res, int *nextLocal) {
	int index = 2;
	int value = 0;

	if (strlen(res) <= 2) return;

	while (res[index]) {
		if (res[index-1] == res[value]) {
			nextLocal[index++] = ++value;
		}
		else if (value == 0) {
			nextLocal[index++] = 0;
		}
		else {
			value = nextLocal[value];
		}
	}
}
        以上就是KMP算法得到nextLocal数组的手工过程的文字性说明以及具体实现代码。下面我们通过图示跟踪配合上面的文字描述以及代码进行说明使我们理解起来更加清晰:

        上述的手工过程看似繁琐,但是通过代码实现其实并不困难,从上文给出的代码可以知道,我们通过一个变量value记录当前需要与目标字符串当前的字符进行比较的字符的下标,这样,我们在图示中最后一步看似繁琐的步骤只要更改一下value的值却不增加index的值就可以实现图示手工过程中继续用当前的字符与前面的字符作比较了。

        在能够得到目标字符串相应位置失配后应有的偏移量这个信息后,我们就可以正式编写KMP串匹配算法了,有了前面的基础,KMP算法已经完成了百分之七八十了,真正实现KMP串匹配的函数并不是很困难。

        前面我们说过,KMP算法最重要的就是控制源字符串的下标的不向前移动,当在某一位置的字符失配后,将目标串的以当前失配字符的下标作为nextLocal数组的下标的值所对应的位置移动到源字符串的失配字符的位置,然后继续从失配字符的后一个字符进行比较。具体代码如下:

int KMPStringMarth(char *resource, char *target) {
	int resLenth = strlen(resource);
	int tarLenth = strlen(target);
	int *nextLocal;
	int resIndex = 0;
	int tarIndex = 0;

	nextLocal = (int *)calloc(sizeof(int), tarLenth);
	getNextLocalArray(target, nextLocal);

	while (resource[resIndex]) {
		while (target[tarIndex] && target[tarIndex] == resource[resIndex]) {
			resIndex++;
			tarIndex++;
		}
		if (target[tarIndex] == 0) {
			free(nextLocal);

			return resIndex - tarIndex;
		}
		else{
			tarIndex = nextLocal[tarIndex];
			if (tarIndex == 0) {
				resIndex++;
			}
		}
	}

	free(nextLocal);
	return NOT_FOUND;
}
        对上述代码的分析:1、我们根据target字符串的长度给出了一个数组,用我们之前编写的getNextLocalArray函数得到相应字符失配后偏移量的信息;

                                        2、给出两个变量resIndex、tarIndex分别用于跟踪源字符串和目标字符串;

                                        3、用一个循环控制源字符串的遍历,逐一进行比较,字符匹配则两个字符串的下标均加一,若不匹配则保持跟踪源字符串的下标不变,让跟踪目标

                                             字符串的下标等于nextLocal中该失配字符所对应的值,并继续进行比较。

                                        4、如果某一次比较时,目标字符串一直到零结束标志都没有失配,则说明找到了目标字符串,返回当前的源字符串中目标字符串的开始位置所对应的

                                             字符的下标;若直到源字符串找到了零结束标志都没有返回,则返回“没找到”。

        以上就是KMP算法的全部分析过程,以后有需要补充的还会继续改善的大笑

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章