串的模式匹配算法---BF、KMP

       寻找字符串S中字符串T出现的位置或者次数的问题属于字符串匹配问题。

       BF算法:

eg:
主串:s="ababcabcacbab";
模式串:t="abc";

      1.变量i,j(初始值为0、1都行)分别指向S、T的第一个位置(这里是指i=1;j=1(i=0;j=0))。

      2.对于每一个字母依次进行比较(i<=s.size&&<=t.size(i<s.length&&<t.length)):

      <1>s[i]=t[j],如果对应位置相等,将i,j后移,继续比较后面的字符。

      <2>s[i]\neq t[j],i,j后退重新匹配,从主串的下一个字符(i-j+2)重新与模式串的第一个字符(j=1)比较。

      3.如果j>t.length,说明匹配成功。返回i-t.length

      算法模板:

int i=1;j=1;
int s=strlen(a);
int t=strlen(b);
while(i<=s&&j<=t){
    if(a[i]==b[i]){//相等:i,j后移,继续比较后续字符
        i++;j++;
    }else{//不相等
        i=i-j+2;//i指向主串的下一个字符
        j=1;//j从模式串的第一个字符开始比较
    }
    if(j>t)
        return i-t;
    return -1;//返回-1表示未找到
}

      hdu2087----剪花布条

     AC代码:

#include<stdio.h>
#include<iostream>
#include<string.h>
#include<stdlib.h>
#include<string>
#include<map>
#include<algorithm>
using namespace std;

const int Max_n=1001; 
char a[Max_n],b[Max_n];

int main(){
    while(~scanf("%s%s",a,b)){
        int sum=0;
        if(a[0]=='#')
            break;
        int s=strlen(a);
        int t=strlen(b);
        int i=0,j=0;//这里注意,我是从下标为0开始的
        while(i<s&&j<t){
            if(a[i]==b[j]){
                i++;
                j++;
            }
            else{
                i=i-j+1;//注意下标为0开始和下标为1开始的区别
                j=0;
            }
            if(j>=t){//如果匹配成功
                j=0;//j再次从0开始,开始一轮新的匹配,
                    //i此时就是成功后最后一个字符的下一个字符
                sum++;//出现次数+1;
                if(i>=s)//i超过主串的长度就跳出
                    break;
            }
        }
        printf("%d\n",sum);
    }
	return 0;
}

     KMP算法:

      next[]数组含义:

      <1>.当主串中的第i个字符与模式串中的第j个字符"失配"(不相等)时,主串中第i个字符(此时i指向的位置不动)

      应该与模式串中的哪个字符在比较。

      <2>.模式串下标x之前的最长等值前后缀的长度(下标从0开始)

        模式串下标x之前的最长等值前后缀的长度+1(下标从1开始)

      next[]计算值证明略。

      kmp、next[]---模板

//下标从1开始(从0开始)
void f(int s){
	net[1]=0;//1号位置为0  (net[0]=-1)
	for(int i=1,j=0;i<s;){  //(for(int i=0,j=-1;i<s;))
		if(j==0||b[i]==b[j]){//j==0,或者此时匹配    //(if(j==-1||b[i]==b[j]))
			i++;
			j++;
			net[i]=j;//i位置就是下标i之前的最长等值前后缀的长度+1
		}else{
			j=net[j];//如果不匹配,j就是i之前长等值前后缀的最后一个字符,
                       也就是下标i之前的最长等值前后缀的长度。
		}
	}
}

int kmp(int s,int t){
	int i=1,j=1;
	while(i<=s&&j<=t){
		if(j==0||a[i]==b[j]){//j=0或者此时匹配,继续下一个位置   (j==-1||a[i]==b[j])
			i++;
			j++;
		}else{
			j=net[j];//否则,主串中i位置的字符应该与net[j]比较
		}
	}
	if(j>t)//匹配成功
		return i-t;
	return -1;
}

      nextval数组含义:.当主串中的第i个字符与模式串中的第j个字符"失配"(不相等)时,主串中第i个字符

      (此时i指向的位置不动)应该与模式串中的哪个字符在比较。计算原理略。

      模板:

void f(int t){
	nextval[1]=0;//1号位置为0
	for(int i=1,j=0;i<t;){
		if(j==0||b[i]==b[j]){//如果匹配
			i++;
			j++;
			if(b[i]!=b[j]){
            //net[i]那个位置(也就是j)与当前位置不等
				nextval[i]=j;
            //nextval[i]的值就是net[j]位置(也就是j)上面的值
			}else{//相等
				nextval[i]=nextval[j];
                //nextval[i]此时的值是j(net[j])
                //位置上面的值也就是nextval[j]
		}else{
			j=nextval[j];
		}
	}
}

hdu1711----Number Sequence

AC代码:

#include<stdio.h>
#include<iostream>
#include<string.h>
#include<math.h>
#include<stdlib.h>
#include<string>
#include<map>
#include<algorithm>
#define inf 0x3f3f3f3f
using namespace std;

const int Max_a=1000010;
const int Max_b=10010;
int a[Max_a],b[Max_b],nextval[Max_b];
void f(int t){
	nextval[1]=0;
	for(int i=1,j=0;i<t;){
		if(j==0||b[i]==b[j]){
			i++;
			j++;
			if(b[i]!=b[j]){
				nextval[i]=j;
			}else{
				nextval[i]=nextval[j];
			}
		}else{
			j=nextval[j];
		}
	}
}
//void f(int t){
//	net[1]=0;
//	for(int i=1,j=0;i<t;){
//		if(j==0||b[i]==b[j]){
//			i++;
//			j++;
//			net[i]=j;
//		}else{
//			j=net[j];
//		}
//	}
//}
int kmp(int s,int t){
	int i=1,j=1;
	while(i<=s&&j<=t){
		if(j==0||a[i]==b[j]){
			i++;
			j++;
		}else{
			j=nextval[j];
		}
	}
	if(j>t)
		return i-t;
	return -1;
}

int main(){
	int t;
	scanf("%d",&t);
	while(t--){
		int s,t;
		scanf("%d%d",&s,&t);
		for(int i=1;i<=s;i++)
			scanf("%d",&a[i]);
		for(int i=1;i<=t;i++)
			scanf("%d",&b[i]);
		f(t);
		printf("%d\n",kmp(s,t));
	}
	return 0; 
}

hdu1686----Oulipo

AC代码:

#include<stdio.h>
#include<iostream>
#include<string.h>
#include<math.h>
#include<stdlib.h>
#include<string>
#include<map>
#include<algorithm>
#define inf 0x3f3f3f3f
using namespace std;

const int Max_a=1000010;
const int Max_b=10010;
char a[Max_a],b[Max_b];
int net[Max_b];

//void f(int t){
//	nextval[1]=0;
//	for(int i=1,j=0;i<t;){
//		if(j==0||b[i]==b[j]){
//			i++;
//			j++;
//			if(b[i]!=b[j]){
//				nextval[i]=j;
//			}else{
//				nextval[i]=nextval[j];
//			}
//		}else{
//			j=nextval[j];
//		}
//	}
//}
void f(int t){
	net[1]=0;
	for(int i=1,j=0;i<=t;){
		if(j==0||b[i]==b[j]){
			i++;
			j++;
			net[i]=j;
		}else{
			j=net[j];
		}
	}
}
int kmp(int s,int t){//此题为统计字符串出现的次数
	int i=1,j=1,sum=0;
	while(i<=s&&j<=t){
		if(j==0||a[i]==b[j]){
			i++;
			j++;
		}else{
			j=net[j];
		}
		if(j>t){//如果匹配成功
			j=net[j];//和下一次应该比较的字符继续比较
			sum++;//出现次数+1
		}
	}
	return sum;
}

int main(){
	int n;
	scanf("%d",&n);
	while(n--){
		scanf("%s%s",b+1,a+1);
		a[0]=b[0]='0';
		int s=strlen(a);
		int t=strlen(b);
		f(t-1);
		printf("%d\n",kmp(s-1,t-1));
	}
	return 0; 
}

hdu2087----剪花布条

AC代码:

#include<stdio.h>
#include<iostream>
#include<string.h>
#include<math.h>
#include<stdlib.h>
#include<string>
#include<map>
#include<algorithm>
#define inf 0x3f3f3f3f
using namespace std;

const int Max_a=1005;
const int Max_b=1005;
char a[Max_a],b[Max_b];
int net[Max_b];

//void f(int t){
//	nextval[1]=0;
//	for(int i=1,j=0;i<t;){
//		if(j==0||b[i]==b[j]){
//			i++;
//			j++;
//			if(b[i]!=b[j]){
//				nextval[i]=j;
//			}else{
//				nextval[i]=nextval[j];
//			}
//		}else{
//			j=nextval[j];
//		}
//	}
//}
void f(int t){
	net[1]=0;
	for(int i=1,j=0;i<=t;){
		if(j==0||b[i]==b[j]){
			i++;
			j++;
			net[i]=j;
		}else{
			j=net[j];
		}
	}
}
int kmp(int s,int t){//与上题不同的是,一次匹配结束后,模式串从头开始再次匹配
	int i=1,j=1,sum=0;
	while(i<=s&&j<=t){
		if(j==0||a[i]==b[j]){
			i++;
			j++;
		}else{
			j=net[j];
		}
		if(j>t){
			j=1;//由于上述原因,此处j从头开始
			sum++;
		}
	}
	return sum;
}

int main(){
	while(~scanf("%s%s",a+1,b+1)&&a[1]!='#'){
		a[0]=b[0]='0';
		int s=strlen(a);
		int t=strlen(b);
		f(t-1);
		printf("%d\n",kmp(s-1,t-1));
	}
	return 0; 
}

hdu3746----Cyclic Nacklace

此题题意是说一个字符串里如果需要把它补成>=2个相同的字符串需要多少个字符。

aaa                   重复的a出现了>=2次,不需要添加字符      0

abca                须补成abcabc                                            2

abcde              须补成abcdeabcde                                    2 

我们只需要求出重复出现的字符串的长度即可(最小循环节)

AC代码:

#include<stdio.h>
#include<iostream>
#include<string.h>
#include<math.h>
#include<stdlib.h>
#include<string>
#include<map>
#include<algorithm>
#define inf 0x3f3f3f3f
using namespace std;

const int Max_a=100005;
char a[Max_a];
int net[Max_a];

void f(int t){
	net[0]=-1;
	for(int i=0,j=-1;i<t;){
		if(j==-1||a[i]==a[j]){
			i++;
			j++;
			net[i]=j;
		}else{
			j=net[j];
		}
	}
}

int main(){
	int n;
	scanf("%d",&n);
	while(n--){
		memset(net,0,sizeof(net));
		scanf("%s",a);
		int t=strlen(a);
		f(t);
		int l=t-net[t];//最小循环节的长度
		if(t%l==0){//字符长度刚好是最小循环节的整数倍
			if(l==t)//最小循环节的长度=字符串的长度   ab
				printf("%d\n",l);
			else//一个字符,或者最小循环节出现了整数个(>=2)
				printf("0\n");
		}else{//不是整数倍
			printf("%d\n",l-t%l);//最小循环节的长度-不是最小循环节那部分多出来字符的个数
		}
	} 
	return 0; 
}

hdu1358----Period

题意:给你一个字符串,某个位置及其之前的字符串如果刚好是最小循环节的整数倍(>1),就输出此时的位置及最小循环节的倍数。

AC代码:

#include<stdio.h>
#include<iostream>
#include<string.h>
#include<math.h>
#include<stdlib.h>
#include<string>
#include<map>
#include<algorithm>
#define inf 0x3f3f3f3f
using namespace std;

const int Max_a=1000005;
char a[Max_a];
int net[Max_a];

void f(int t){
	net[0]=-1;
	for(int i=0,j=-1;i<t;){
		if(j==-1||a[i]==a[j]){
			i++;
			j++;
			net[i]=j;
		}else{
			j=net[j];
		}
	}
}

int main(){
	int n,m=0;
	while(~scanf("%d",&n)&&n){
		memset(net,0,sizeof(net));
		scanf("%s",a);
		f(n);
		printf("Test case #%d\n",++m); 
		for(int i=2;i<=n;i++){//根据题意循环直接从2开始
			int l=i-net[i];//求出当前前缀的最小循环节
			if(i%l==0&&(i/l>1))//如果是当前长度的整数倍&&(>1)j即可输出
				printf("%d %d\n",i,i/l);
		}
		printf("\n");//控制两个示例之间的空格
	} 
	return 0; 
}

POJ2406----Power Strings               AC代码

POJ2752----Seek the Name, Seek the Fame

题意:给你一个字符串,找出一个子串,使得这个字串既是这个字符串的前缀,也是这个字符串的后缀。

首先是一个整个字符串区间,他的最长前后缀找到以后肯定是满足条件的一个字符串,我们在用next数组,将j滑到最长前缀的最后一个字符的位置,我们一直重复这样的过程,直到到第一个位置为止。

AC代码:

#include<stdio.h>
#include<iostream>
#include<string.h>
#include<math.h>
#include<stdlib.h>
#include<string>
#include<map>
#include<algorithm>
#define inf 0x3f3f3f3f
using namespace std;

const int Max_a=400005;
char a[Max_a];
int net[Max_a],b[Max_a];

void f(int t){
	net[0]=-1;
	for(int i=0,j=-1;i<t;){
		if(j==-1||a[i]==a[j]){
			i++;
			j++;
			net[i]=j;
		}else{
			j=net[j];
		}
	}
}

int main(){
	while(~scanf("%s",a)){
		memset(net,0,sizeof(net));
		memset(b,0,sizeof(b));
		int n=strlen(a);
		f(n);
		int j=n;
		int i=1;
		while(j!=0){//第一个位置是循环出口
			b[i++]=j;//用数组来存放要输出的位置
			j=net[j];//j需要一直滑动
		}
		for(int j=i-1;j>1;j--)
			printf("%d ",b[j]);
		printf("%d\n",b[1]);
	} 
	return 0; 
}

hdu2594----Simpsons’ Hidden Talents

题意:找第一个字符串的前缀和第二个字符串的后缀一样的最长长度

AC代码:

解法一:我们把两个字符串拼接求其最长前后缀的长度,但是这里可能会超过第一个字符串的长度,或者大于第二个字符的长度,所以需要递归,直到长度<=第一个字符串的长度(第二个字符串的长度)。

#include<stdio.h>
#include<iostream>
#include<string.h>
#include<math.h>
#include<stdlib.h>
#include<string>
#include<map>
#include<algorithm>
#define inf 0x3f3f3f3f
using namespace std;

const int Max_a=100005;
char a[Max_a],b[Max_a];//这里注意数组长度,因为要拼接,刚开始就是因为这个程序一直超时
int net[Max_a];

void f(int t){
	net[0]=-1;
	for(int i=0,j=-1;i<t;){
		if(j==-1||a[i]==a[j]){
			i++;
			j++;
			net[i]=j;
		}else{
			j=net[j];
		}
	}
}

int f(int j,int mmin){
	if(j>mmin)//如果当前所求长度大于字符串最小长度
		return f(net[j],mmin);//j滑向下一个位置再次重复上面的过程
	return j;//最后返回满足题意的长度
}
int main(){
	while(~scanf("%s%s",a,b)){
		memset(net,0,sizeof(net));
		int a1=strlen(a);
		int b1=strlen(b);
		for(int i=a1,j=0;j<b1;i++,j++)
			a[i]=b[j];//拼接字符串
		int n=a1+b1;
		f(n);
		int j=net[n];//前后缀匹配最长的字符串长度
		int mmin=min(a1,b1);//两个字符串长度的最小值
		int mmax=f(j,mmin);
		if(mmax!=0){//如果能找到输出字符和长度
			a[mmax]='\0';//这里只是为了方便输出
			printf("%s %d\n",a,mmax);	
		}
		else//否则输出0
			printf("0\n");
	} 
	return 0; 
}

解法二:按照kmp来做

#include<stdio.h>
#include<iostream>
#include<string.h>
#include<math.h>
#include<stdlib.h>
#include<string>
#include<map>
#include<algorithm>
#define inf 0x3f3f3f3f
using namespace std;

const int Max_a=50005;
char a[Max_a],b[Max_a];
int net[Max_a];

void f(int t){
	net[0]=-1;
	for(int i=0,j=-1;i<t;){
		if(j==-1||a[i]==a[j]){
			i++;
			j++;
			net[i]=j;
		}else{
			j=net[j];
		}
	}
}

int kmp(int s,int t){
	int i=0,j=0;
	while(i<s&&j<t){
		if(j==-1||b[i]==a[j]){
			i++;j++;
		}else{
			j=net[j];
		}
		if(j>=t&&i!=s)//当匹配成功的时候,如果此时的i没有到最后一个字符的下一个位置
			j=net[j];//主串继续与模式串的net[j]位置上面的字符继续比较
	}
    //最后返回i匹配到最后j的位置(即能够匹配的最大长度)
	return j;
	
}
int main(){
	while(~scanf("%s%s",a,b)){//a为模式串,b为主串
		memset(net,0,sizeof(net));//此处必须初始化net数组
		int a1=strlen(a);
		int b1=strlen(b);
		f(a1);
		int mmax=kmp(b1,a1);//让两个字符串进行模式匹配
		a[mmax]='\0';//这里为了方便输出
		if(mmax==0)//未匹配成功
			printf("0\n");
		else
			printf("%s %d\n",a,mmax);
	} 
	return 0; 
}

转载于:https://www.cnblogs.com/zut-syp/p/10543710.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值