算法学习之KMP算法,包你学会!

算法学习之KMP算法,包你学会!

时间复杂度:o(m+n)

特点:每当一趟匹配过程中出现字符比较不等时,不需回溯主串迭代器,而是利用已经得到的“部分匹配”的结果将模式串向右“滑动”尽可能远的一段距离后(即移动模式串迭代器)继续比较。

相关概念

  • 字符串前缀:必须包含第一个字符,但不包含最后一个字符的连续子串。(aba 的字符串前缀有:a ab)
  • 字符串后缀:必须包含最后一个字符,但不包含第一个字符的连续子串。(aba 的字符串后缀有: a ba)
  • 最长相等前缀和后缀:就是最长的相同的前缀和后缀。

算法精髓:主串与模式串匹配过程中比较模式串中的第k个字符不等时,模式串迭代器重新指向模式串前k个字符的最长相等前缀的后一个字符,再继续比较。

算法原理:

在这里插入图片描述

正在比较a与c,很明显不相等。主串的迭代指针不动,模式串的迭代指针应该回溯到最长相等前缀的后一个字符,即‘a’。然后再继续比较。

在这里插入图片描述

这是因为当主串中的某个字符与模式串中的第k个字符不等时,包括该字符在内的前k个字符与模式串中的前k个字符拥有相同的最长相等前后缀。因为该字符的前k-1个字符是和模式串的前k-1个字符完全相等的。

在这里插入图片描述

应用实例:在主串“abababc”中查找“ababc”

我们已经知道了KMP算法就是在比较过程中不断地移动模式串指针到最长相等前缀后一个字符。但还有一些细节需要我们处理:

  1. 什么时候移动主串迭代器?

    在字符匹配相等时、当模式串迭代器指向首字符前一个字符时(比如数组索引为-1)

  2. 什么时候返回结果?

    在模式串迭代器指向末尾字符后一个字符时,在主串迭代器指向末尾字符后一个字符时

  3. 模式串中所有子串(包括自己)的最长相等前后缀的长度怎么求?

    暂且按下不表,假设我们已经求出,并将其存放在next数组中

代码:

typedef struct str {
	char* val;
	int length;
}String;

int* getNext(String sub);

//使用KMP算法得到模式串的索引
//str:主串
//sub:模式串
//返回:找不到返回-1,否则返回首字符在主串中的索引
int indexOf_kmp(String str, String sub) {
	int i = 0, j = 0;//i,j分别迭代主串、模式串
	int* next = getNext(sub);//next数组存储模式串所有子串的最长相等前后缀的长度
	while (i < str.length-1 && j < sub.length-1) {
		if (j == -1 || str.val[i] == sub.val[j]) {
			i++; j++;
		}
		else {
			j = next[j];//next[j]存储着前j个字符的最长相等前后缀的长度,而最长相等前缀后一个字符的索引正好等于该长度
		}
	}
    free(next);
	if (i == str.length)
		return -1;
	return i - 1 - sub.length;
}

求解next数组

算法精髓:两个迭代器初始时刻一个指向第一个字符,一个指向第二个字符,求next[j]只需比较两个迭代器指向的字符,如果相等则next[j]=next[i]+1,再分别增加两个迭代器。如果不相等,则令第一个迭代器指向第next[i]个字符,再进行比较,相等则next[j]=next[next[i]]+1。如果不相等,则继续令第一个迭代器指向第next[next[i]]个字符……直到相等再分别增加两个迭代器。(其实就是反复使用KMP算法精髓

在求解之前,我们先来规定一下一些特殊情况:

  1. 第一个字符不存在前缀、后缀,规定next[0]=-1
  2. 如果一直比较不相等,直到第一个迭代器指向next[0],即-1时,令next[j]=0

算法原理:

在这里插入图片描述

若一个椭圆代表一个字符,假设两个迭代器指向的字符相等,则next[j]=next[i]+1

在这里插入图片描述

假设两个迭代器指向的字符不等,且next[i]=7,则i=7

在这里插入图片描述

如果还不相等,假设next[7]=3,则i=3

在这里插入图片描述

如果相等了,则第一个蓝框和最后一个蓝框向右扩展一格,即next[j]=next[next[i]]+1。如果不相等则继续让i=next[i],直到i=-1或者相等。

代码:

int* getNext(String sub) {
	int* next = (int*)malloc(sizeof(int)*sub.length);
	next[0] = -1;
	int i = 0, j = 1;
	while (j < sub.length) {
		if (i == -1) {
			i = 0;
			next[j] = 0;
			j++;
			
		}
		else if (sub.val[i] == sub.val[j]) {
			next[j] = i + 1;
			i++;
			j++;
		}
		else {
			i = next[i];
		}
	}
	return next;
}

可以发现存在一些冗余代码,我们优化一下:

int* getNext(String sub) {
	int* next = (int*)malloc(sizeof(int)*sub.length);
	next[0] = -1;
	int i = 0, j = 1;
	while (j < sub.length || sub.val[i] == sub.val[j]) {
		if (i == -1) {
			next[j++] = ++i;
		}
		else {
			i = next[i];
		}
	}
	return next;
}

大功告成!

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值