BF+KMP

文章详细介绍了KMP算法,用于从主串中高效地查找子串。首先解释了暴力求解方法的时间复杂度,然后重点阐述了KMP算法的核心——next数组和nextval数组的计算,以及如何在匹配失败时进行有效回退,以达到O(m+n)的时间复杂度。最后,给出了KMP算法的实现代码示例。
摘要由CSDN通过智能技术生成

算法作用:从字符串中寻找是否存在子串

BF

暴力求解,定义下标i,j开始从头遍历,i和j相同就一起+1,遇到不同的字符就让j返回到子串0,i回到开始位置+1

时间复杂度O(m*n)

//str:主串
//sub:子串
//如果找到返回在str中的下标,找不到返回-1
int BF(char* str,char* sub)//暴力求解 时间复杂度 m*n 
{
	assert(str&&sub);
	int lenstr = strlen(str);
	int lensub = strlen(sub);
	if (lenstr == 0 || lensub == 0)
	{
		return -1;
	}
	int i = 0;
	int j = 0;
	while (i < lenstr && j < lensub)
	{
		if (str[i] == sub[j])
		{
			i++;
			j++;
		}
		else
		{
			j = 0;//j返回最开始
			i = i - j + 1;//令i回到开始位置+1
		}
	}
	if (j >= lensub)//走完了子串 找到子串
	{
		return i-j;//返回是子串时i开始位置
	}
	return -1;

}

KMP

再遍历过程中主串的i不回退,子串j要回退到next数组的位置

next数组就是 保存子串某个位置匹配失败后,j回退的位置

如果能够再主串中找到和子串匹配的一部分

此时看到5和2又可以继续匹配,但是c不等于a那么 j又要继续回退到2所在的next数组位置

如何求next数组

next[ j ] = k   k就是j要回退的位置下标

规定 next[0] = -1  next[1]=0

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

j=7 那么寻找0-6 是否有以a开头,b结尾的真子串,不包含本身

那么也就是0-1 a b 和5 - 6  a b 长度是2 那么下标7的K=2

再此例子中  子串是可以重合的

上面是手求,在遍历过程中如何求k值呢

next [ 0 ] =  -1 next [ 1 ] = ?    next [ j ] = k  next [ j+1 ]=?

如果从0位置开始想有些不好想,假设next [ j ]=k 就可以写出

这里面还有一个条件是p [ k ] = = p [ i -1 ] 才有p [ i ]  = k +1 

那么如果不相等呢?p [ k ] ! = p [ i -1 ] 那么p [ i ] = ?

规律 如果next [ j ] = k  , next [ j + 1] 如果要是增加 那么肯定是+1

                                                          那么要是减少 不一定是减到0 

在子串回退的过程中 会多次出现回退到 j = 0  k = -1   也就是回退到-1的情况

等于根本没有找到a 开头 b结尾的真子串  那么就是 k = -1   k + 1 = 0

也就是在写代码过程中,k == -1 也要进入循环

这种回退到-1的情况 可能会多次出现

kmp时间复杂度

O(m+n)求next数组需要遍历一遍子串m 主串比较需要遍历比较n次 

NextVal数组优化

 

在4 匹配失败  j 要回退到3 -> 2  ->1 ->0 ->-1  为什么不能一次性回退到-1呢 既然他们都是a

规则:

1、回退到的位置和当前字符一样,就写回退那个位置的nextval值。

2、如果回退到的位置和当前字符不一样,就写当前字符原来的next值。

此时代码还是和求next的K一样 但是不同是 要根据规则判断

写了两种方式 一种是j=0 开始依次求nextval

另一种是j = 2 求next 

区别在于控制的是 j 和 j-1

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

void GetNextVal(const char* sub, int* nextval, int lenSub)
{
	int i = 0;
	int k = -1;//前一项的k
	nextval[0] = -1;
	while (i < lenSub)
	{
		if (k == -1||sub[i] == sub[k])
		{
			i++;
			//next[i] = k+1;
			k++;
			if (sub[i] != sub[k])
			{
				nextval[i] = k;
			}
			else
			{
				nextval[i] = nextval[k];
			}
		}
		else
		{
			k = nextval[k];
		}
	}
}

void GetNext(const char* sub,int* next,int lenSub)
{
	next[0] = -1;
	if (lenSub == 1)
	{
		return;
	}
	next[1] = 0;
	int i = 2;//当前i下标
	int k = 0;//前一项的k
	while (i < lenSub)
	{
		if (k == -1||sub[i - 1] == sub[k])
		{
			next[i] = k + 1;
			i++;
			k++;
		}
		else
		{
			k = next[k];//K回退
		}
	}
}
//str:代表主串
//sub:代表子串
//pos:代表从主串的pos位置开始找
int kmp(const char* str,const char* sub,int pos)
{
	assert(str && sub);
	int lenStr =  strlen(str);
	int lenSub = strlen(sub);
	if (lenStr == 0 || lenSub == 0) return -1;
	if (pos < 0 || pos >= lenStr) return -1;//pos范围0~lenStr-1 判断是否越界.
	
	//int* next=(int*)malloc(sizeof(int) * lenSub);
	//assert(next!=NULL);
	GetNext(sub, next,lenSub);

	int* nextval = (int*)malloc(sizeof(int) * lenSub);
	assert(nextval != NULL);
	GetNextVal(sub, nextval, lenSub);

	int i = pos;//遍历主串
	int j = 0;//遍历子串
	while (i < lenStr && j < lenSub)
	{
		if (j == -1||str[i] == sub[j])
		{
			i++;
			j++;
		}
		else
		{
			j = nextval[j];//如果第一个就不相等 j会回退到-1 上面就进入 i++ j++==0 继续匹配
		}//回退到相等(str[i] == sub[j])如果能成功匹配就和BF一样
	}
	if (j >= lenSub)
	{
		return i - j;//参考bf算法 求起始位置  画图
	}
	return -1;//j>=lenStr跳出 说明没有找到返回-1

}
int main()
{
	//printf("%d\n", BF("ababcabcdabcde", "abcd"));//5
	//printf("%d\n", BF("ababcabcdabcde", "abcdf"));//-1
	//printf("%d\n", BF("ababcabcdabcde", "ab"));//0


	printf("%d\n", kmp("ababcabcdabcde", "abcd",0));//5
	printf("%d\n", kmp("ababcabcdabcde", "abcdf",0));//-1
	printf("%d\n", kmp("ababcabcdabcde", "ab",0));//0

	//printf("%d\n", kmp("babcde", "aaaaab", 0));
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值