KMP算法(c语言实现)

前言

我们在对比字符串时若发现有一个字符不满足,则从下一个字符重新匹配,挨个遍历的算法是一种低效的,于是三位前辈,D.E.Kunth, J.H.Morris, V.R.Pratt发表乐一个模式匹配算法,可以大大避免重复遍历的情况,我们把它称之为克努特——莫里斯——普拉斯算法,简称KMP算法。


一、KMP模式匹配算法原理

KMP算法(c语言实现)_第1张图片

我们不直接讲代码,我们先理解一下他为什么比朴素算法好,按照我们上面说的朴素算法进行匹配,此时前五个字母,两个串完全相等,直到第六个字母,c和d不相等,接下来按照朴素算法应该从第一个串的第二个对比,不相等,再到第三个对比,直到第四个字母a和下面串的a相等,这样从主串一步步移动,似乎没什么问题,但是我们仔细观察发现字串a与他后面的b,c都不相同,而在我们第一次匹配时,主串的前五位和子串的前五位相同,那么字串的a肯定和主串二三位置的b,c不等,所以我们将主串移动到第二个位置,和第三个位置的操作是多余的。所以我们应该直接忽略这两个操作,直接将主串挪到第四个位置a的位置开始进行匹配,从这里我们可以分析得到在字符串对比中有些回溯是可以不用发生的——所谓好马不吃回头草,我们的kmp算法就是为了让不必要的回溯不发生而诞生的。

二、KMP算法的具体操作

1.回溯的步数

上文我们提到了若两个字符不相等时,主串会回溯,我们的KMP算法可以不让主串进行回溯,我们是通过移动子串继续匹配的,如上图所示,我们利用kmp算法可以将字串移到主串的第四个位置进行比对,假设字串的位置由j表示,此时我们也就是移到了j = 3的位置继续和主串的“c”进行比较,既然我们是移动子串,那么这个回溯步数就和子串有特定的联系,这里引入一个概念,字串的前后缀,例如“abc”,它的前缀有“a“,”ab“,后缀有”c”,“bc”,我们回溯的步数是根据字串的前后缀相似度,也就是最长相等前后缀,例如“abcabd”我们的j = 6(j为字串的字母位置)时,发现d与主串的c不等,需要移动,这个最长前后缀是在d之前的“abcab”中寻找的,我们可以很容易得到最长前后缀为“ab”,所以我们移到了j = 3的位置,也就是str【2】,综上我们每一个位置的字母都有一个回溯的步数,这里我们引入KMP算法中的next数组:
next数组是一个我们存回溯步数的数组,规则为第一个字母存-1,有相同前后缀的存前后缀的大小,其余存0;
KMP算法(c语言实现)_第2张图片
c语言实现代码如下:

void GetNext(str t,int next[]) {
     
	int j = 0, k = 1;
	next[0] = -1; //令第一个为-1
	while (j < t->length - 1) {
     	
		if (k==-1 || t->data[j] == t->data[k]) {
     	
			j++;
			k++;
			next[j] = k;
       	} else {
     
			k = next[k]; 不等时回溯
		}
	}
 }

2.KMP算法函数代码:

代码如下所示:

int KMPIndex(str s, str t) {
     
	int next[MaxSize], i = 0, j = 0;
	GetNext(t, next);
	while (i < s->length && j < t->length) {
     
		if (j == -1 || s->data[i] == t->data[j]) {
     
			i++;
			j++;  		
		} else {
     
			j=next[j]; //不相等时根据next数组回溯
		}
		
    }
    if (j >= t->length) {
     
    	return(i - t->length);			//若字串在主串中,返回字串在主串中的第一个位置。
	} else {
     
		return(-1);						//若字串不存在,则返回—1;
	}
}

总结

kmp算法适用与处理子串重复出现一些字母的情况,我们在处理这类问题时可以尝试使用kmp算法,初学kmp,有许多不足的地方,还请大家多多包涵。

你可能感兴趣的:(字符串)