KMP算法实现strstr函数

发布于:2022-12-14 ⋅ 阅读:(607) ⋅ 点赞:(0)

什么是KMP算法:

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

                                                                                                ------来自百度百科

要了解KMP算法,我们首先了解一下传统BF算法。

     BF算法,即暴力(Brute Force)算法,是普通的模式匹配算法,BF算法的思想就是将目标串S的第一个字符与模式串T的第一个字符进行匹配,若相等,则继续比较S的第二个字符和 T的第二个字符;若不相等,则比较S的第二个字符和T的第一个字符,依次比较下去,直到得出最后的匹配结果。BF算法是一种蛮力算法。

                                                           -                                     -----来自百度百科

BF算法是怎么样的呢。我们先用一幅图解释一下。

以C语言库函数strstr为例我们用BF算法模拟实现一下。

char *  strstr (const char * str1, const char * str2) {
        char *cp = (char *) str1;
        char *s1, *s2;
        if ( !*str2 )
            return((char *)str1);
        while (*cp)
       {
                s1 = cp;
                s2 = (char *) str2;
                while ( *s1 && *s2 && !(*s1-*s2) )
                        s1++, s2++;
                if (!*s2)
                        return(cp);
                cp++;
       }
        return(NULL);

为什么乘BF算法为蛮力算法呢!我们来了解一下KMP算法与它的主要区别。

KMP和BF的根本区别就在于,主串的i是不会回退的,而j视情况回退。

我们观察上图,当匹配到5位置时,匹配失败,既然能匹配到此位置说明至少子串j之前的部分必定是相同的。博友们仔细体会一下这句话。

   观察上图,如果i不回退,j回退到2位置是不是就是最好的情况,因为子串2位置之前的部分就等于主串i前面的部分。相比于BF算法减少了很多不必要的工作,使得程序更加高效。那我们如何能知道在5位置匹配失败后就让j回退到2位置呢。

那我们就顺利的引出了KMP算法的精髓next数组:也就是用next[j]=k;来表示不同的j来对应一个k值,这个k值就是将来要移动的j所要移动到的位置。

我们的k值是这样求得:

1.规则:找到匹配成功部分的两个相等的真子串(不包含本身),一个以下标0开始,另一个以下标j-1结尾。

2.不管什么数据next[0] = -1,next[1] = 0,

给博友们两个练习,答案就在下方,了解一下next数组的求法:

       设主串为数组a,子串为数组b。我们假设next[j] = k的话那就有,b[0]b[1]...b[k-1] = a[x]a[x+1]...a[i-1]。假设a[i]=b[k]的话,b[0]b[1]...b[k-1]b[k] = a[x]a[x+1]...a[i-1]a[i],那就有next[j+1] = k+1.那现在只要能知道a[i]!=b[k]时next[j+1]即可。当a[i]!=b[k]时我们令k=next[k],再去比较next[j+1] = k+1,直到k=-1时都无法匹配,那么next[j+1]=0.

        至此为止我们已经知道next数组的求法。我们利用KMP算法重新实现strstr函数

void get_next(char* sub,int* next, int size)
{
	next[0] = -1;
	next[1] = 0;
	int i = 2;
	int k = 0;
	while (i < size)
	{
		if (k==-1||sub[i - 1] == sub[k])
		{
			next[i] = k + 1;
			i++;
			k++;
		}
		else
		{
			k = next[k];

		}
	}

}

//str1:代表主串;
//str2:代表子串,

char* my_strstr(const char* str1, const char* str2)
{
	char* start = str1;
	assert(str1 != NULL && str2 != NULL);
	int* next = malloc(sizeof(int) * strlen(str2));
	int lenStr = strlen(str1);
	int lenSub = strlen(str2);
	if (lenStr == 0 || lenSub == 0) return NULL;
	get_next(str2, next, strlen(str2));
	int i = 0, j = 0;
	while (i < lenStr && j < lenSub)
	{
		if (j == -1 || str1[i] == str2[j])
		{
			i++;
			j++;
		}
		else
		{
			j = next[j];
		}
	}
	if (j == lenSub)
		return start + (i - j);



}
int main()
{
	char arr[20] = "100866666";
	char arr2[] = "866";

	//int* k = malloc(sizeof(int));
	printf("%s", my_strstr(arr, arr2));
	return 0;

}

运行结果如下。

不足之处欢迎博友们指正!