KMP算法总结(二)

本文详细介绍了KMP算法的代码实现过程,通过构建prefix-table来指导字符串匹配,并通过实例解释了如何根据前缀和公共前后缀关系填充表格。文章还讨论了在遇到匹配失败时如何处理,以及防止数组越界的方法,最后给出了完整的KMP算法实现代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

说完了KMP算法的一些基本的操作,我们再来说一下KMP算法的代码实现:

写代码的时候和上面我们所说的操作都是一样的:

首先我们需要完成的就是prefix-table的构建,为了大家能更好的理解,我们使用一串新的字符串作为例子进行讲解:

我们写出它所有的前缀和最长公共前后缀的长度为:

我们从中可以发现一个规律:

例如:我们拿字符串:“A  B   A   B   C   A”和字符串:“A   B   A   B   C   A   B”举例

第一段字符串的公共前后缀的长度为1 ,也就是说第一个和最后一个肯定是相等的,那么如果我们要使得其公共前后缀长度增加的话,我们要在最后一位添加一个和第二个字母相等的字母,也就是说我们的字符串“A  B   A   B   C   A”,如果想让它的最长公共前后缀的长度变为2,就需要在A的后面加上一个B。

同理,如果字符串“A   B   A   B   C   A   B”也想让自己的长度+1,它的最前面两个字符串是A  B,最后面两个字符串也是A  B,也就得在字符 B 的后面加上和前面第二位B后面相同的字符,也就是A,这样就得到了最长公共前后缀长度为3 的字符串“A   B   A   B   C   A   B   A”,所以我们可以看出,每一个字符串和每一个字符串之间是有关系的,这样的关系就可以 指导我们填写prefix-table和编写代码:

还是拿上面的那个字符串来进行举例:

假如我们现在要填写6号下标的最长公共前后缀的长度,我们就可以是使用绿线以前的信息作为参考:

那么我们可以从前面的字符串得到什么信息呢:前面的字符串是“A   B   A   B   C   A”,并且5号下标的最长公共前后缀的长度是1 ,也就是说,前面的字符串只有一个A和最后一个A是相同的,所以如果要让6号下标的数字变成2,唯一的办法就是看6号下标的字符是否和1号下标的字符相等,也就是我们只需检测6号下标的字符是否为B。我们设len表示达到的最长公共前后缀的长度,i标售当前要比较的字符,所以我们就需要判断P[len] == P[i],如果相等的话,我们就可以让len++,并且让prefix[i] = len;那如果匹配不上的话该怎么办,我们可能会想:直接填0就好了。直接填0 是没错,但是它并不适用于最后一个字母,   

就如上面的情况,最后一个匹失败,正确结果是1,填0就会出错。这里解决的办法就是将len向后移动一位,将当前len的prefix里面的长度作为新的len进行比较也就是len = prefix[len-1],这个办法还是存在一个小的问题,有可能会造成数组越界,因为都是向后prefix数组向后走的,所以还需要再判断一次。这样,我们的prefix-table就构造好了,下面只需要将每一位进行后移,第一位填写-1。

void prefix_table(char pattern[], int prefix[], int n) {
	prefix[0] = 0; //前缀表的第0位一定为0
	int len = 0; //比较的长度,初始值为0
	int i = 1; //检测第i个字母,从第1个字母开始比较
	while (i < n)
	{
		if (pattern[i] == pattern[len])
		{
			len++;
			prefix[i] = len;
			i++;
		}
		else if (len > 0)
		{
			len = prefix[len - 1];
		}
		else
		{
			prefix[i] = len;
			i++;
		}
	}
}

下面是完整的代码:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>


/*pattern : 子串   prefix : 前缀表   n : 前缀表的长度 */
void prefix_table(char pattern[], int prefix[], int n) {
	prefix[0] = 0; //前缀表的第0位一定为0
	int len = 0; //比较的长度,初始值为0
	int i = 1; //检测第i个字母,从第1个字母开始比较
	while (i < n)
	{
		if (pattern[i] == pattern[len])
		{
			len++;
			prefix[i] = len;
			i++;
		}
		else if (len > 0)
		{
			len = prefix[len - 1];
		}
		else
		{
			prefix[i] = len;
			i++;
		}
	}
}


void move_prefix_table(int prefix[], int n)
{
	for (int i = n; i > 0; i--)
	{
		prefix[i] = prefix[i - 1];
	}
	prefix[0] = -1;
}


/*传参:text:主串
*		pattern: 待匹配字符串
* */
void KMP_Search(char text[], char pattern[])
{
	bool flag = true;
	int n = (int)strlen(pattern);//待匹配字符串长度
	int m = (int)strlen(text);//主串字符串长度
	int i = 0; //定位主串text[i]
	int j = 0; //定位待匹配串pattern[j]
			   //动态申请内存作为前缀表
	int * prefix = (int *)malloc(sizeof(int)*n);
	//生成前缀表
	prefix_table(pattern, prefix, n);
	//移动前缀表
	move_prefix_table(prefix, n);
	//进行匹配操作
	while (i < m)
	{
		if (j == n - 1 && text[i] == pattern[j])
		{
			flag = false;
			printf("find pattern at index:%d\n", i - j);
			j = prefix[j];
		}
		if (text[i] == pattern[j])
		{
			i++;
			j++;
		}
		else
		{
			j = prefix[j];
			if (j == -1)
			{
				i++;
				j++;
			}
		}
	}
	if (flag)
	{
		printf("can't find the pattern!\n");
	}
}


int main()
{
	char pattern[] = "abcde";
	char text[] = "ababcabcdeacbab";
 	KMP_Search(text, pattern);
	return 0;
}

运行测试代码也是没有问题的:

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值