深入了解kmp

kmp

    kmp算法就是为了避免字符串匹配中出现重复遍历的情况
kmp匹配算法的原理
    S串:ababaaaba
    T串:ababaac
    如果采用bf(暴力匹配)的话,第一次已经匹配到c才发现匹配不上,然后就会重新将T串的首字母a与S串的第二个字母b进行匹配,这样就会增加很多无用的匹配,时间复杂度也大。如图
在这里插入图片描述

    但是如果采用kmp算法,就不需要一个一个比较。kmp的核心就在于最长公共前后缀,但是为什么要用最长公共前后缀?最长公共前后缀是字符串移位的关键,因为我们要把前缀的位置直接移到后缀的位置,来举个例子
在这里插入图片描述
现在比较到母串比较到B,子串比较到A,发现不一样,又发现,字串中有公共前后缀AB。
在这里插入图片描述

所以直接就把前面的AB移动到后面的AB当中。
在这里插入图片描述
这样可以保证当前指针的前面上下是匹配的
在这里插入图片描述

这时候就有人有疑问了,万一中间的也有匹配上的呢,但事实是不会有的。可以走一下看看,现在子串往前走一步
在这里插入图片描述
发现匹配不上,就再走一步
在这里插入图片描述
还是不匹配,再走一步
在这里插入图片描述
发现只有公共前缀移动到公共后缀时才匹配上了

kmp算法利用了子串本身的特征,当子串的第n位匹配不上时,接下来就要寻找能与n位前面的i个(i表示子串(n-1)对应的最长公共前后缀的长度)字符匹配上的,也就是找子串的前i位,子串的前i位与母串n位置的前i位对其一定是匹配的,所以只需要从第1+1的位置继续匹配。所以next数组就是最长公共前后缀加1.
next数组的求法

我们能确定next的数组的第0位是-1,后面求解每一位的next的值时
根据前一位进行比较,从第一位开始
将其前一位的字符与其前一位的next值所对应的字符相比较,如果相等
则该位的next的值就是前一位的next的值加1,如果不等,
继续寻找next值对应的内容来与前一位进行比较,
直到找到某个位上内容的next的值对应的内容与前一位相等
则这个位对应的值加上1即为需求的next值
如果找到第一位都没有找到与前一位相等的内容,那么求解位上的next值为next[0]加1

void getnext(char* t, int* next) {
	int l = strlen(t);
	next[0] = -1;
	int i = 1;
	int k = 0;
	while (i <= l) {
		if (k == 0||t[next[k]] == t[i - 1] ) {
			next[i] = next[k] + 1;
			i++;
			k = i - 1;
		}
		else k = next[k];
	}
}

next_value的求法

nextval数组是在next数组的基础上求得的,如果i所指向的字符,与next[i]所指向的字符相等,就可以直接把nextval[next[i]]的值赋给nextval[i],如果不相等,nextval[i]就是next[i]


void getnext_val(char* t, int* next, int* nextval) {
	int i = 1;
	nextval[0]=-1;
	int l = strlen(t);
	while (i <= l) {
		if (t[next[i]] == t[i]) nextval[i] = nextval[next[i]];
		else nextval[i] = next[i];
		i++;
	}
}

kmp的主要算法

如果两个指针指向的字符串相等,则两指针加加,否则,j回退到nextval[j]的位置

int kmp(char* s, char* t,int* nextval) {
	int i = 0;
	int j = 0;
	int l1 = strlen(s);
	int l2 = strlen(t);
	while (i < l1 && j < l2) {
		if (j == -1||s[i] == t[j] ) {
			i++;
			j++;
		}
		else j = nextval[j];
	}
	if (j == l2) return i - l2 ;
	else return -1;
}

在这里插入图片描述

接下来是完整的kmp算法的代码

#include<stdio.h>
#include<string.h>
//当字符串索引是从0开始的,next和nextval数组下标都是从0开始的
//且索引为1的时候两个数组的值都是-1,因为next数组第一个元素是-1
//所以要遍历整个字符串,所以是<=,nextval数组也是一样,当到kmp时,就不用=了
void getnext(char* t, int* next) {
	int l = strlen(t);
	next[0] = -1;
	int i = 1;
	int k = 0;
	while (i <= l) {
		if (t[next[k]] == t[i - 1] || k == 0) {
			next[i] = next[k] + 1;
			i++;
			k = i - 1;
		}
		else k = next[k];
	}
}

void getnext_val(char* t, int* next, int* nextval) {
	int i = 1;
	nextval[0]=-1;
	int l = strlen(t);
	while (i <= l) {
		if (t[next[i]] == t[i]) nextval[i] = nextval[next[i]];
		else nextval[i] = next[i];
		i++;
	}
}

int kmp(char* s, char* t,int* nextval) {
	int i = 0;
	int j = 0;
	int l1 = strlen(s);
	int l2 = strlen(t);
	while (i < l1 && j < l2) {
		if (s[i] == t[j] || j == -1) {
			i++;
			j++;
		}
		else j = nextval[j];
	}
	if (j == l2) return i - l2 ;
	else return -1;
}

int main(void) {
	char s[100],t[100];
	scanf("%s", s);
	scanf("%s", t);
	int next[100] = { 0 };
	getnext(t, next);
   int nextval[100];
	getnext_val(t, next, nextval);
	int m = kmp(s, t,nextval);
	printf("%d", m);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值