c语言实现kmp

时间:2022-08-02 01:23:00

KMP介绍

KMP算法是一种改进的字符串匹配算法。
KMP算法的核心是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数。
KMP与BF不一样的地方是,主串的 i 不会回退,模式串的 j 也不会直接移动到0位置

c语言实现kmp
j回到下标2的位置就很合适
目的:i不回退,j回退到合适的位置
因为i不回退,所以我们尽量在主串中找到与模式串匹配的一部分串
假设我们找到了(i能和j匹配到这里,说明前面有一部分匹配成功),绿色框的两个相同,又0…1和3…4相同,所以橙色的两个相同
c语言实现kmp
p[0]…p[1] 和 p[3]…p[4]长度相同且为2,那么 j 就回退到2的位置

j 要移动的位置由next数组确定,next数组保存了模式串在某个位置匹配失败后,回退的位置
c语言实现kmp
next[j]=k
k是 j 要移动的位置

k的值求法:
1.找到匹配成功部分的两个相等的真子串(不包含本身),一个以下标0开始,一个以下标 j -1结尾
2.next[0]=-1,next[1]=0
c语言实现kmp
练习手求next数组:
c语言实现kmp
c语言实现kmp
那么已知next[i]=k,怎么求next[i+1]=?

假设 i 匹配失败后要回退到的位置是k
c语言实现kmp
此时如果p[i]==p[k]
那么我们就可以知道i+1匹配失败后要回退的位置
c语言实现kmp
如果p[i] != p[k]
k=next[k]继续回退,直到k= -1
c语言实现kmp
如果回退到0下标后还是没有找到,k已经= -1了还是没有找到,那么next[i+1]=0

KMP代码

//str主串
//sub子串
//pos开始检查的开始位置
//str_len主串长度
//sub_len子串长度
int KMP(char* str, char* sub, int pos)
{
	assert(str && sub);
	int str_len = strlen(str);
	int sub_len = strlen(sub);
	if (str_len == 0 || sub_len == 0 || sub_len>str_len)
	{
		return -1;
	}
	if(pos<0 || pos>=str_len)
	{
	    return -1;
	}
	int* next = (int*)malloc(sub_len * sizeof(int));
	if (next == NULL)
	{
		perror("malloc");
		return -1;
	}
	InitNext(sub, next, sub_len);//初始化子串的next数组

	int i = pos;//遍历主串
	int j = 0;//遍历子串
	while (i < str_len && j < sub_len)
	{
		if (j==-1 || str[i] == sub[j])
		{
			i++;
			j++;
		}
		else//j回退到next[j]
		{
			j = next[j];
//极端情况下:i
//           a b c ...
//           p b c ...
//           j
//极端情况下:j刚开时和i就匹配不上,j = next[j];因为next[0]=-1,j=-1
		}
	}

	free(next);
	next=NULL;

	if (j >= sub_len)
	{
		return i - j;
	}
	else
	{
		return -1;
	}
}

next数组的初始化

next[0]= -1,next[1]=0,i=2,k=0
c语言实现kmp
前面说到如果p[i]==p[k]
那么我们就可以知道i+1匹配失败后要回退的位置

这时候我们把i当成i+1,k当成k+1;想知道i匹配失败后回退的位置,就先求p[i-1]==p[k]成不成立

void InitNext(char* sub, int* next, int sub_len)
{
	assert(sub && next);
	next[0] = -1;
	
	if(sub_len==1)
	return;
	
	next[1] = 0;
	int i = 2;//代表当前位置的i
	int k = 0;//代表前一项的k

	while (i < sub_len)
	{
		if (k==-1 || sub[i - 1] == sub[k])
		{
			next[i] = k + 1;
			i++;
			k++;
		}
		else
		{
			k = next[k];//只要匹配不成功就回退,直到k= -1进行特殊处理
		}
	}
}

整体代码

void InitNext(char* sub, int* next, int sub_len)
{
	assert(sub && next);
	next[0] = -1;
	if(sub_len==1)
	return;
	
	next[1] = 0;
	int i = 2;//代表当前位置的i
	int k = 0;//代表前一项的k

	while (i < sub_len)
	{
		if (k==-1 || sub[i - 1] == sub[k])
		{
			next[i] = k + 1;
			i++;
			k++;
		}
		else
		{
			k = next[k];//只要匹配不成功就回退,直到k= -1进行特殊处理
		}
	}
}
//str主串
//sub子串
//pos开始检查的开始位置
//str_len主串长度
//sub_len子串长度
int KMP(char* str, char* sub, int pos)
{
	assert(str && sub);
	int str_len = strlen(str);
	int sub_len = strlen(sub);
	if (str_len == 0 || sub_len == 0 || sub_len>str_len)
	{
		return -1;
	}
	if(pos<0 || pos>=str_len)
	{
	    return -1;
	}
	int* next = (int*)malloc(sub_len * sizeof(int));
	if (next == NULL)
	{
		perror("malloc");
		return -1;
	}
	InitNext(sub, next, sub_len);//初始化子串的next数组

	int i = pos;//遍历主串
	int j = 0;//遍历子串
	while (i < str_len && j < sub_len)
	{
		if (j==-1 || str[i] == sub[j])
		{
			i++;
			j++;
		}
		else//j回退到next[j]
		{
			j = next[j];
//极端情况下:i
//           a b c ...
//           p b c ...
//           j
//极端情况下:j刚开时和i就匹配不上,j = next[j];因为next[0]=-1,j=-1
		}
	}
		
	free(next);
	next=NULL;
	
	if (j >= sub_len)
	{
		return i - j;
	}
	else
	{
		return -1;
	}
}
int main()
{
	int ret = KMP("abdabccd", "abc", 0);
	printf("%d\n", ret);
	return 0;
}

next数组的优化

c语言实现kmp

i 可以一步回退到位,而不是一直一直回退
c语言实现kmp
练习:
c语言实现kmp