从strstr到KMP【c语言】

strstr

字符串查找函数

char *strstr( const char *string, const char *strCharSet );

Find a substring.
    
Return Value

Each of these functions returns a pointer to the first occurrence of strCharSet in string, or NULL if strCharSet does not appear in string. If strCharSet points to a string of zero length, the function returns string.//返回第一次找到的地址

#include<stdio.h>
#include<string.h>
int main()
{
	char arr1[] = "abcdefabcdef";
	char arr2[] = "bcd";
	char* ret = strstr(arr1, arr2);
	if (NULL == ret)
	{
		printf("找不到\n");
	}
	else
	{
		printf("%s\n", ret);//bcdefabcdef
	}
	return 0;
}
模拟实现strstr

abbbcdefbbcdef
bbc
第一次匹配失败时,得回到str的第二个b上重新开始,但是由于如果直接让str和substr移动的话,就找不到一开始的起始位置了,因此用s1,s2来替代
每一次查找失败,需要下次开始时多移动一步,就需要有个cur来++
动手画一下图就好理解了

image-20220119200616893

char* my_strstr(const char* str, const char* substr)
{
	const char* s1 = str;
	const char* s2 = substr;
	const char* cur = str;
	assert(str && substr);
	if (*substr == '\0')
	{
		return (char*)str;
        //str本身是安全的指针,有const修饰,如果不强制类型转换,返回成char*这个不安全指针会报警告
	}
	while (*cur != '\0')
	{
		s1 = cur;
		s2 = substr;
		while (*s1 != '\0' && *s2 != '\0' && *s1==*s2)//优先级 ==  >  !=  >  &&
      //可以优化为 while (*s1 && *s2 && *s1==*s2 )
		{
			s1++;
			s2++;
		}
		if (*s2 == '\0')
		{
			return (char*)cur;
		}
		cur++;
	}
	return NULL;//cur从头走到尾了都找不到
}
int main()
{
	char arr1[] = "abbbcdefbbcdef";
	char arr2[] = "bbc";
	char* ret = my_strstr(arr1, arr2);
	if (NULL == ret)
	{
		printf("找不到\n");
	}
	else
	{
		printf("%s\n", ret);//bbcdefbbcdef
	}
	return 0;
}

缺点:算法效率较低,可以用KMP算法实现

库函数源代码
#include<string.h>
 char*
strstr(register const char* s1, register const char* s2)
{
	while(s1 && *s1) {
		if(strncmp(s1, s2, strlen(s2)) == 0)
			return ( char*)s1;
		s1 = strchr(s1+1, *s2);
	}
	return NULL;
}

KMP算法

一种改进的字符串匹配算法,核心是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的

1.为什么主串i不回退

image-20220119204151159

2.j的回退位置

image-20220119204215644

目的:i不回退,j回退到一个特定的位置

image-20220119204538586

问题:假设有这么一个串,怎么能确定这个j回退的位置呢

因为i不回退,所以要尽量在主串中找到和子串匹配的一部分串

所以第一次j回退到2的位置

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

image-20220119205215930

求next数组:

如果找不到2个相等的真子串,那么此时next里面放的就是0

image-20220119205841832

image-20220119210320278

找到的话,next数组里放的就是那两个串单独的长度

a b a b c a b c d a b c d e

-1 0 0 1 2 0 1 2 0 0 1 2 0 0

image-20220119211324740

一次最多增加1

假设next[i] = k 推出公式:

image-20220119211937908

又长度相等,即k-1-0 = i-1-x
则x = i-k

如果p[i] == p[k],中间的是p数组,8下标是a,3下标也是a
给左边加上p[k],给右边加上p[i]

image-20220119212307418

那么在next[i] = k的基础上且p[i] == p[k],推出了,next[i+1] = k+1

如果p[i] != p[k]呢?
那就一直回退,直到找到p[i] == p[k],就能利用公式next[i+1] = k+1

image-20220119213054998

此时next[6] = 1

KMP实现

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

void GetNext(const char* sub, int* next, int lenSub)
{
	next[0] = -1;
	if (lenSub == 1)//只有1个元素,next数组只能赋值1个
	{
		return;
	}
	next[1] = 0;
	int i = 2;
	int k = 0;//i前一项的k
	//注意手算next数组与用代码算next数组区别,i还没求,得先求i-1
	//手算:p[i] == p[k] --》next[i+1] = k+1
	//代码算:p[i-1] == p[k] --》next[i] = k+1
	while (i<lenSub)
	{
		if (k == -1 || sub[i-1] == sub[k])
		{
			next[i] = k + 1;
			i++;
			k++;
		}
		else//不相等,k需要往回退到next对应的下标处,再看p[i-1] == p[k]是否成立
			//如果k一直回退,退到-1,此时k越界了,且说明中间是找不到2段相等的子串,即next[i]=0
		{
			k = next[k];
		}
	}
}


int KMP(const char* str, const char* substr, int pos)
{
	assert(str && substr);
	int lenStr = strlen(str);
	int lenSub = strlen(substr);
	if (lenStr == 0 || lenSub == 0)
	{
		return -1;
	}
	if (pos < 0 || pos >= lenStr)
	{
		return -1;
	}

	int* next = (int*)malloc(sizeof(int)*lenSub);//开辟相应大小的next数组
	assert(next != NULL);

	GetNext(substr, next, lenSub);

	int i = pos;//遍历主串
	int j = 0;//遍历子串

	while (i < lenStr && j < lenSub)
	{
		if (j == -1 || str[i] == substr[j])
		{
			i++;
			j++;
		}
		//注意,如果第一个字符就匹配失败,j会回到-1,会产生数组越界
		//此时j恰好需要++回到0,i也应该指向下一个
		else
		{
			j = next[j];//不相等回退到next数组中相应的j下标
		}
	}
	free(next);//next置空
	if (j >= lenSub)
	{
		return i - j;//找到了
	}
	return -1;//遍历完之后都找不到
}


int main()
{
	printf("%d\n", KMP("ababcabcdabcde", "abcd", 0));//5
	printf("%d\n", KMP("ababcabcdabcde", "abcdf", 0));//-1 找不到
	printf("%d\n", KMP("ababcabcdabcde", "ab", 0));//0 一开始就有
	return 0;
}

代码算next数组

image-20220119231925970

k回退到-1

image-20220119232020163

next数组优化

0	1	2	3	4	5	6	7	8

a	a	a	a	a	a	a	a	b

-1	0	1	2	3	4	5	6	7

假设在5下标匹配失败,就要回到4位置,再回到3位置,一直回到0位置
为何不一步回到0位置呢?前面的字符都一样,第5个不匹配,前面肯定也都不匹配

nextval数组:

0	1	2	3	4	5	6	7	8

a	a	a	a	a	a	a	a	b

-1	0	1	2	3	4	5	6	7	--next值

-1	-1	-1	-1	-1	-1	-1	-1	7	 --nextval值

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

image-20220119233837083

选项答案第一个next值是从0开始的,所以需要+1

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值