数据结构4——字符串匹配

一、定义

在主字符串(主串)中查找子字符串(子串),如果能够找到子串,则返回子串的首字符的位置。

如果未找到,则返回-1。

二、实现字符串匹配的两种算法

(一)、BF算法(暴力求解)

 1.理解

对于子串中的每个字符,都在主串中遍历,如果两者字符相同,则子串和主串都向后移动,如果不相同,则子串中的字符回退到子串的首字符,主串中的字符回退到这一趟失配开始位置的下一个位置

①循环同时遍历主串和子串。如果有一个遍历到结尾则退出循环。

②判断主串和子串的字符是否相等,如果相等,则主串和子串均向后移动,如果不相等,则让子串回到首字符的位置,让主串回到这一趟失配开始位置的下一个位置。

③对循环退出的条件判断是否能够找到子串的位置。

 如果子串能够越界,则表示找到了子串的位置。

 2.画图理解

 3.代码实现

(1)从主串首字符开始匹配
int BF_Matching(const char str[],const char sub[])
{
	//1.循环遍历两个字符串
	//①求两个字符串的长度
	int len_str=strlen(str);//不计算'\0'
	int len_sub=strlen(sub);
	//②定义两个变量遍历字符串
	int i,j=0;
	//③循环遍历 
	while(i<len_str&&j<len_sub)
	{
		//2.判断是否相同
		//①相同,都向后移动 
		if(str[i]==sub[j])
		{
			i++;
			j++;
		} 
		//②不同
		else
		{
			i=i-j+1;//主串回到失配开始位置的下一个位置
			j=0;//子串回到首字符位置 
		} 
	 } 
	//3.判断退出的条件
	if(j<len_sub)
	{
		return -1;
	} 
	return i-j;
}
(2)从主串特定位置开始匹配
int BF_Matching_Pos(const char str[],const char sub[],int pos)
{
	//  1.获取两个字符串的长度
	int len_str=strlen(str);
	int len_sub=strlen(sub);
	//2.循环同时遍历两个字符串
	int i=pos;//主串从特定位置开始
	int j=0; 
	while(i<len_str&&j<len_sub)
	{
		//判断
		if(str[i]==sub[j]) // 相同,则继续
		{
			i++;
			j++; 
		} 
		else  //不同 
		{
			i=i-j+1;//i回退到失配开始位置的下一个位置 
			j=0;//j回退到首字符位置 
		}
	}
	//判断循环退出的条件
	if(j<len_sub)
	{
		return -1;
	} 
	return i-j;//适配位置 
}

(二)、KMP算法(对BF算法的优化)

1.理解

BF算法通过不断让主串回退到开始失配位置的下一个位置,而KMP算法则不让主串回退。

分两种情况:

(1)子串的首字符在后续的子串字符中未出现:此时主串不需要回退,回退也会发生失配,无意义。

(2)子串的首字符在后续的子串字符中出现:此时主串也不需要回退,虽然回退有意义,但此时在失配位置之前的字符串与主串仍然相等,所以通过让子串回退到合适位置,从而代替主串的回退。

结论:此两种情况覆盖了所有可能性,即主串可以不回退,只是让子串回退到合适位置。

2.画图理解

(1)子串的首字符在后续子串字符中未出现

(2)子串的首字符在后续的子串字符中出现

3.寻找子串的回退位置

由上图可以看出,子串的回退位置其实就是左红或者上紫的长度。

(1)next数组存放回退位置:

由于子串的每一个位置都可能发生失配的现象(需要回退),针对此种存放多个数据的情况,可以用next数组存放每个失配位置的回退位置。

 (2)  next数组赋值方法:

①前两个字符赋值-1和0。

②求下一个字符的next值:

如果当前已知位置的字符和回退位置的字符相同,则下一个字符next=当前回退位置+1。

如果当前已知位置的字符和回退位置字符不相同,则让其不断回退比较,直到回退到-1。此时下一个字符next=0。

(3)nextval数组:是对next数组的优化

如果当前位置的字符和回退位置的字符相同,且当前位置字符与主串的字符匹配不上,那么回退位置的字符与主串也肯定匹配不上,所以回退无价值。

(4)nextval数组的赋值方法:

①前一个字符赋值为-1。

②根据当前字符的next求当前字符的nextval:

如果当前位置的字符与其回退位置(next)的字符相同,则当前位置nextval=回退位置nextval。

如果当前位置的字符与其回退位置(next)的字符不相同,则当前位置nextval=当前位置的next。

注意:如果要求nextval数组,要先知道next数组。

4.代码实现

(1)通过next数组实现
int* Get_Next_Val(const char sub[],int len)
{
	//1.分配空间:数组分配无法确定数组的下标 
	//采用动态申请内存  不知道预先数组的长度,所以动态申请较好 
	int* next=(int*)malloc(sizeof(int)*len);
	if(next==NULL)
		exit(1);	
	//2.实现next数组:
	//前两个位置分别赋值-1,0 
	next[0]=-1;
	next[1]=0;
	int k=next[1] ;//k表示当前的回退位置,k不停在变化
	//通过已知位置得出下一个位置 
	int i=1;
	while(i<len-1)
	{
		//回退位置  k   //回退位置字符  sub[k] 
		//如果当前已知字符与回退位置字符相同,则下一个位置的回退位置+1 
		//如果回退到-1,表示触底了,则下一个位置的回退位置=当前回退位置+1 
		if(k==-1||sub[i]==sub[k])
		{
			i++;//下一个位置 
			k++; //回退位置+1 
			next[i]=k;
		}
		//如果当前已知字符与回退位置字符不相同,则继续回退
		else
		{
			k=next[k];
		}
	}
	return next;
}


int KMP_Pos(const char str[],const char sub[],int pos)
{
	//1.循环遍历两个字符串
	//①求两个字符串的长度
	int len_str=strlen(str);//不计算'\0'
	int len_sub=strlen(sub);
	//②定义两个变量遍历字符串
	int i=pos;
	int j=0;   
	//③循环遍历 
	//定义next数组
	int* next=Get_Next_Val(sub,len_sub);
	while(i<len_str&&j<len_sub)
	{
		//2.判断是否相同
		//①相同,都向后移动 
		//j如果回退到-1,表示子串的字符与主串不相同,则主串和子串均要移动 
		if(j==-1||str[i]==sub[j])
		{
			i++;
			j++;
		} 
		//②不同,i不回退,j回退到合适位置 (j可能回退到-1) 
		else
		{
			j=next[j]; 
			
		} 
	} 
	//3.判断退出的条件
	if(j>=len_sub)
	{
		return i-j;
	}

    free(next);
    return -1;
 } 
(2)通过nextval数组实现
int* Get_Excellent_Next(const char sub[],int len)
{
    int* next=Get_Next_Val(sub,len); //获取next数组 
	//1.分配空间:数组分配无法确定数组的下标 
	//采用动态申请内存  不知道预先数组的长度,所以申请较好 
	int* nextval=(int*)malloc(sizeof(int)*len);
	if(nextval==NULL)
		exit(1);
		
	//2.实现nextval数组:
	//前一个位置赋值-1
	nextval[0]=-1;
	//通过已知位置得出下一个位置 
	for(int i=1;i<len;i++)
	{
		//如果当前已知字符与回退位置字符相同,则nextval=回退位置nextval 
		if(sub[i]==sub[next[i]])
		{
			nextval[i]=nextval[next[i]];
		}
		//如果当前已知字符与回退位置字符不相同,则nextval=回退位置的next 
		else
		{
			nextval[i]=next[i];
		
		}
	}
	free(next);
	return nextval;
} 

int KMP_Pos_Nextval(const char str[],const char sub[],int pos)
{
	//1.循环遍历两个字符串
	//①求两个字符串的长度
	int len_str=strlen(str);//不计算'\0'
	int len_sub=strlen(sub);
	//②定义两个变量遍历字符串
	int i=pos;
	int j=0;   
	//③循环遍历 
	//获取nextval数组
	int* nextval=Get_Excellent_Next(sub,len_sub);
	while(i<len_str&&j<len_sub)
	{
		//2.判断是否相同
		//①相同,都向后移动 
		//j如果回退到-1,表示子串的字符与主串不相同,则主串和子串均要移动 
		if(j==-1||str[i]==sub[j])
		{
			i++;
			j++;
		} 
		//②不同,i不回退,j回退到合适位置 (j可能回退到-1) 
		else
		{
			j=nextval[j]; 
			
		} 
	} 
	//3.判断退出的条件
	if(j>=len_sub)
	{
		return i-j;
	}
	free(nextval); 
	return -1;
 } 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值