数据结构 字符串的匹配

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

  • 目录

  • 暴力匹配

  • KMP

  • Sunday

  • shift_and


暴力匹配

暴力匹配,是单模匹配中一种不大“聪明”算法,先将母串s的第一位和字串(模式串)的第一位对齐,然后向后匹配,如果没有匹配上,然后用母串的第二位去和模式串第一位对齐,再匹配,以此类推,直到完全匹配。假设母串的长度是n,模式串的长度为m,时间复杂度为O(n*m)。

代码如下(示例):

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
char text[50],pattern[11];
int brute_force (){
	int m,cnt=0;
	for(int i=0;i<strlen(text)-strlen(pattern)+1;i++){
		m=i,cnt=0;
		for(int j=0;j<=strlen(pattern);j++){
			if(text[m]!=pattern[j]){
				break;
			} 
			cnt++,m++;
			if(cnt==strlen(pattern))
				return i;
		}
	}
	return -1;
}
int main()
{
	//gets(text);
	cin>>text;
	//getchar();
	//gets(pattern);
	cin>>pattern;
	cout<<pattern;
	//puts(pattern);
	//cout<<strlen(pattern);
	cout<<brute_force();
	return 0;
 } 

我发现那个字符串char用gets没法读单个字符,只能用scanf或者cin,在不用string里getline的情况下怎么读入空格,求大佬指点。

KMP算法

假设母串的长度是n,模式串的长度为m,最优时间复杂度O(m+n) ,最差时间复杂度O(n*m),平均O(n+m);

假设此时已经匹配到了第五位,且第六位失配,我们观察发现母串的一二位和四五位相同,此时,把我们的模式串往后跳三位,会发现前两位匹配一定是成功的,那么我们就可以直接把我们此时不匹配的第六位和模式串的第三位进行匹配,如果再失配我们就拿着此时的失配信息往前找,进行相同的匹配。由此我们可以看出,相比暴力匹配,我们节省了大量的匹配操作。

过程1:

母串

                a e c a e a e c a e d

模式串

                a e c a e d

过程2:

母串

                a e c a e a e c a e d

模式串

                         a e c a e d

过程3:

母串

                a e c a e a e c a e d

模式串

                                a e c a e d

代码如下(示例):

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
char s[50],p[11];
int KMP(){
	int lens=strlen(s),lenp=strlen(p);
	int next[lenp];//next数组跟模式串有关系
	next[0]=-1;
	//初始化next数组
	for(int i=1,j=-1;i<lenp;i++){ //j指向当前字符的前一个next数组的值
		while(j!=-1&&p[j+1]!=p[i])
			j=next[j];
		if(p[j+1]==p[i])j+=1;//整体长度加一位 
		next[i]=j;
	}
	for(int i=0,j=-1;s[i];i++){
		while(j!=-1&&p[j+1]!=s[i])
			j=next[j];//调整j的位置 
		if(p[j+1]==s[i])j+=1;//整体匹配长度加一位 
		if(p[j+1]==0)//模式串已经匹配成功
			return i-lenp+1;//头部的位置
			 
	}
	return -1;
} 
int main()
{
	cin>>s;
	cin>>p;
	//cout<<p;
	cout<<KMP();
	return 0;
 } 
 

Sunday算法

假设母串的长度是n,模式串的长度为m,最优时间复杂度O(n/m) ,最差时间复杂度O(n*m),平均O(n+m);

Sunday算法也是要节省掉一些不必要的匹配操作,回想我们的暴力算法,假设后移一位可以匹配成功,母串最后是e,我们可以知道此时模式串最后一位是e,那么更合理的一个方式,我们找到模式串中最后一个e,使母串的e和模式串的e对齐,这个e就是对齐点,然后再从头匹配。

过程1:

母串

                a e c a e a e c a e d

模式串

                ……e…....

过程2:

母串

                a e c a e a e c a e d

模式串

                           ……e

                      a e c a e d  

做一个假设,若是母串s中的对齐点没有在模式串中出现过,此时我们设模式串最前面有一个-1位可以匹配任意字符。

母串

                a e c a e a e c a e d

模式串

            -1 a e c a e d  

代码如下(示例):

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
char s[50],p[11];
int Sunday(){
	//初始化每一种字符最后一次出现的位置
	int ind[150]={0},lenp=strlen(p),lens=strlen(s);
	for(int i=0;i<150;i++){
		ind[i]=lenp+1;
	}
	for(int i=0;p[i];i++){
		ind[p[i]]=lenp-1;
	} 
	for(int i=0;i<lens-lenp+1;){
		int f=1;
		for(int j=0;j<lenp;j++){
			if(p[j]==s[i+j])
				continue;
			i+=ind[s[i+lenp]];//尽可能往后跳 
			f=0;
			break;
		}
		if(f==1) return i;
	}
	
	return -1;
} 
int main()
{
	cin>>s;
	cin>>p;
	//cout<<p;
	cout<<Sunday();
	return 0;
 } 

SHIFT_AND算法

假设母串的长度是n,模式串的长度为m,k是p的整形长度,最优时间复杂度O(n*m/k) ,最差时间复杂度O(n*m/k),平均O(n*m/k),通常时间复杂度O(n);

SHIFT_AND算法时间复杂度与模式串没关系,即对模式串做了提前预处理。预处理是将模式串中的字符转化为二进制位,这样我们可以利用一些位运算进行处理。

模式串处理:

                a e c a e d  

                ch[a]:1 0 0 1 0 0-----9

                ch[c]:0 0 1 0 0 0-----4

                ch[d]:0 0 0 0 0 1-----32

                ch[e]:0 1 0 0 1 0-----18

                      p:0 0 0 0 0 0

p所代表的状态是母串s处理到某位设为k,如果p的某一位为1,从第k位往前找到的前缀长度。设k=4即第k位为e,此时可以往前找到ae,aecae,即p=010010。由此可以看出当p的第五位为1时,匹配成功。那么p的状态每一次是如何转换的呢?

        p = ( p << 1 | 1) & ch[s[k]]

如果上一个状态匹配成功了第一位,那么再来一个字符就有可能匹配成功第二位,但是匹配成功的前提是这个原字符在第二位上出现过,即&运算。p左移一位末尾是自动补0但是第一位是有可能匹配成功的,所以强制赋1。

如何判断匹配成功呢?

        p & ( 1 << ( n - 1 ) ) ?= 1

即判断p的第n位是不是1,为1,找到了,不为1,往后处理,即p存储了多个状态。其实p是一个仅用位的微型nfa。

母串

                a e c a e a e c a e d

模式串处理结果:

                ch[a]:1 0 0 1 0 0-----9

                ch[c]:0 0 1 0 0 0-----4

                ch[d]:0 0 0 0 0 1-----32

                ch[e]:0 1 0 0 1 0-----18

                      p:0 0 0 0 0 0

代码如下(示例):

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
char s[50],p[11];
int code[150]={0},l=0;	//首先要记录字符的编码; 
int Shift_And(){

	for(;p[l];l++){
		code[p[l]] |=(1 << l);//在第几位出现过 
	}
	int pp=0;
	for(int i=0;s[i];i++){//扫描母串每一位 
		pp=(pp<<1|1)&code[s[i]];
		if(pp&(1<<(l-1)))//判断pp所代表的模式串的最高位是不是为一 
			return i-l+1;//返回头部位置
		 
	}
	return -1; 
}
int main()
{
	cin>>s;
	cin>>p;
	//cout<<p;
	cout<<Shift_And();
	return 0;
 } 

总结

这是我第一次写博客,可能有些不完善或是错误,欢迎各位大佬指点* v *.最近在学习数据结构与算法,感觉还是自己写博客可以加深理解和记忆,个人觉得刷题也是很必要的,一定要自己编写一些代码才能加深算法理解。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值