串的模式匹配

串的模式匹配

概念

就是找到模式串在主串中从第pos个字符后首次👈出现的位置

串的模式匹配算法👇

BF算法🎈

思路

Brute-Force算法又称“蛮力匹配”算法,从主串S的第pos个字符开始,和模式串T的第一个字符比较,若相等,则继续逐个比较后续字符;遇到不相等的情况就回溯到主串第pos + 1 个字符,重新开始和模式串T的第一个字符及后续字符进行逐个比较。以此类推,直到模式串T中的每一个字符依次和主串S中的一个连续字符串全部相等,则模式匹配成功,此时返回模式串T的第一个字符在主串S的位置;否则主串中没有和模式串相等的字符串,称模式匹配不成功,返回0

实现
int Index(SString S , int pos , SString T)
{
	int i = pos , j = 1; //主串从第pos位置开始,模式串从第1个位置开始
	while (i <= S.len && j <= T.len)
	{
		if (S.ch[i] == T.ch[j]) //当对应字符相等时,比较后续字符 
		{
			i++;
			j++;
		}
		else //当对应字符不相等时 
		{
			i = i - j + 2; //主串回溯到 i - j + 2 的位置重新比较 
			j = 1;         //模式串从头开始重新比较 
		}
	}
	if (j > T.len) //匹配成功时,返回匹配的起始位置 
		return i - T.len; 
	else 
		return 0;  //匹配失败时,返回 0  
}
  • 例1:

​ S : Hello World!

​ T : llo

​ 返回 :3

  • 例2:

​ S : oooool

​ T : ol

​ 返回 :5

注意:为了方便理解,串的位序都是从 1 开始的

效率

在最坏的情况下,即每个子串的前m - 1 个字符都和模式串匹配,只有第 m 个字符不匹配时,BF算法的时间复杂度为 O(n×m),其中n和m分别是主串和模式串的长度。这个算法的缺点是:不匹配时,主串的扫描指针经常回溯,会造成重复的比较而导致时间开销增加👀。

KMP算法🚩

思路

每当一次匹配过程中出现字符不匹配的情况时,主串的指示指针 i 不用回溯,而是利用已经得到的“部分匹配”结果将模式串向右“滑动”尽可能远的距离后继续进行比较

实现

KMP算法是在已知模式串的next 数组的值的基础上执行的,所以需要先实现 next[] 数组,这是实现KMP算法的一个关键点也是难点。

引入两个前置概念:

  • 串的前缀:包含第一个字符且不包含最后一个字符的子串

  • 串的后缀:包含最后一个字符且不包含第一个字符的子串

next数组的手算方法:当第 j 个字符匹配失败,由前 1 ~ j-1 个字符组成的串记为 Q ,则 next [ j ] = Q 的最长公共前后缀(即前后缀的相等)的长度 + 1

例:给一模式串:ababaa,则它的next数组的每个元素值如下:

序号 j123456
模式串ababaa
next [ j ]011234

当j = 1 时,默认都为 0 ,则 next[ 1 ] = 0;

当 j = 2 时,那就看 1 ~ j - 1 那段字符的公共前后缀,观察可知此时没有公共前后缀,则next[ 2 ] = 0 + 1 = 1 ;

当 j = 3时,此时没有公共前后缀,则next[ 3 ] = 0 + 1 = 1 ;

当 j = 4时,此时的公共前后缀为 a ,长度为1,则next[ 4 ] = 1 + 1 = 2 ;

当 j = 5时,此时的公共前后缀为 ab ,长度为2,则next[ 5 ] = 2 + 1 = 3 ;

当 j = 6时,此时的公共前后缀为 aba ,长度为3,则next[ 6 ] = 3 + 1 = 4 。

//求模式串T的next数组
void get_next(SString T , int next[])
{
	int i = 1 , j = 0;
	next[1] = 0;
	while(i < T.len)
	{
		if(j == 0 || T.ch[i] == T.ch[j])
		{
			i++;
			j++;
			next[i] = j;
		}
		else
			j = next[j]; 
	}
} 

//KMP 算法
int Index_KMP(SString S , int pos , SString T)
{
	int i = pos , j = 1; //主串从pos 开始,模式串从头开始 
	int next[T.len + 1];
	get_next(T,next);    //求模式串的next数组 
	while(i <= S.len && j <= T.len)
	{
		if(j == 0 || S.ch[i] == T.ch[j]) // 继续比较后续字符 
		{
			i++;
			j++;
		}
		else
			j = next[j]; //模式串的指示指针 j 移动,即模式串向后移动 
	}
	if(j > T.len)
		return i - T.len; //匹配成功,返回匹配的起始位置 
	else
		return 0;		  //匹配失败,返回 0 
}
效率

KMP算法的不同点是,当主串与模式串不匹配时,主串指针 i 不回溯,只移动模式串的指针 j 。求模式串的next数组时间复杂度为O(m),再加上KPM算法里的循环,整个算法的时间复杂度为O(m + n)

优化

上述定义的next函数在某些情况下尚有缺陷。假设主串为 ‘goolggoogle’,模式串为‘google’

image-20221014213734578 image-20221014214830597

当发现第四个字符不匹配的时候,按照next数组,j 会回退到 1 ,即主串中的第四个字符 ‘l’ 会和模式串的第一个字符 ‘g’ 匹配,但是我们发现,第一个字符和前一个字符是一样的都是 ‘g’ ,所以当前主串字符 ‘l’ 和第四个字符不匹配,那也不会和第一个字符匹配,所以这次比较是毫无意义的💔。

处理的方式就是优化数组,使用nextval数组去替代next数组

image-20221014214754921

再看一个例子📄:

image-20221014215429060

这个模式串的next数组如下:

image-20221014224417479

在写nextval数组时,从左往右开始:

nextval [ 1 ] = 0 是固定的

第二个字符和第一个字符相等,所以nextval [ 2 ] = nextval [ 1 ] = 0

第三个字符和第二个字符相等,所以nextval [ 3 ] = nextval [ 2 ] = 0

第四个字符和第三个字符相等,所以nextval [ 4 ] = nextval [ 3 ] = 0

第五个字符和第四个字符不相等,所以nextval [ 5 ] = next [ 5 ] = 4 不变

所以nextval数组如下:

image-20221014230447362

nextval数组的求法📌:

​ 先算出next数组且令nextval[1] = 0

for (int j = 2 ; j <= T.len ; j++)
{
	if (T.ch[next[j]] == T.ch[j])
		nextval[j] = nextval[next[j]];
	else
		nextval[j] = next[j];
}

所以KMP算法的优化就是next数组的优化:当子串和模式串不匹配时 j = nextval [ j ];

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不怕娜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值