字符串匹配的KMP算法

首先,KMP算法求解什么类型问题?

    那就是字符串匹配问题。给你两个字符串,寻找其中一个字符串是否包含另一个字符串,如果包含,返回包含的起始位置。

 

例如:问题描述

A=“aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab”
B=“aaaaaaaab”

判断B是不是A的子串。

 

对于诸如此类的问题都可以利用KMP算法来解决。它的核心就是:每一趟比较出现字符不相同时,不需要回溯索引指针i,而是利用已经得到的部分匹配的结果将子串向右滑动尽可能远的距离,继续进行比较。也就是说,i是不断增加的,随着i的增加j相应地变化。

 

next数组的求解方法是:第一位的next值为0,第二位的next值为1,后面求解每一位的next值时,根据前一位进行比较。首先将前一位与其next值对应的内容进行比较,如果相等,则该位的next值就是前一位的next值加上1;如果不等,向前继续寻找next值对应的内容来与前一位进行比较,直到找到某个位上内容的next值对应的内容与前一位相等为止,则这个位对应的值加上1即为需求的next值;如果找到第一位都没有找到与前一位相等的内容,那么需求的位上的next值即为1。

 

对于求next数组总的来说有两种方案:

(1):利用数据结构中的方法求next数组,代码如下:

void getnext(){
    int i=1,j=0;
    next[1]=0;
    while(i<T.lenth){
        if(j==0||T.ch[i]==T.ch[j]){
            ++i,++j;
            next[i]=j;
        }
        else j=next[j];
    }
}

增进后的nextval数组求法如下:

void getnextval(){
    int i=1,j=0;
    nextval[1]=0;
    while(i<T.lenth){
        if(j==0 || T.ch[i]==T.ch[j]){
            ++i,++j;
            if(T.ch[i]!=T.ch[j]) nextval[i]=j;
            else nextval[i]=nextval[j];
        }
        else j=nextval[j];
    }
}

(2)第二种方法就是最长公共前后缀求法。

想要详细了解请看:https://blog.csdn.net/wenbingoon/article/details/8974239

next数组求法如下:

void getnet(){
    int i=1,j=0;
    while(i<m){
        if(b[i]==b[j]) nxt[i++]=++j;
        else if(!j) i++;
        else j=nxt[j-1];
    }
}

利用next数组实现kmp算法的代码如下:

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;

const int maxn=1e5+10;

int next[maxn];
int nextval[maxn];

typedef struct{
	char ch[maxn];
	int lenth;
}String;

String S,T;   //母串S,子串T

void getnext(){			//求模式串T的next函数值并存入next
	int i=1,j=0;
	next[1]=0;
	while(i<T.lenth){
		if(j==0||T.ch[i]==T.ch[j]){
			++i,++j;
			next[i]=j;
		}
		else j=next[j];
	}
}

int KMP()
{												//利用模式串T的next函数求T在主串S中的位置
	int i=1,j=1;
	while(i<=S.lenth && j<=T.lenth){			//两个串均未比较到串尾
		if(j==0 || S.ch[i]==T.ch[j])			//继续比较后继字符
			++i,++j;
		else j=next[j];							//模式串向右移动
	}
	if(j>T.lenth) return i-T.lenth;			//匹配成功
	else return 0;								//匹配失败
}

int main(){
	int ans;
	memset(next,0,sizeof(next));
	memset(nextval,0,sizeof(nextval));
	scanf("%s %s",S.ch+1,T.ch+1);
	S.lenth=strlen(S.ch+1);
	T.lenth=strlen(T.ch+1);
	getnext();
	ans=KMP();
	if(!ans) puts("匹配失败");
	else printf("在%d处匹配成功\n",ans);
	return 0;
}

 

在next的基础上对数组修改后得到nextval数组。利用nextval数组实现kmp算法的代码如下:

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;

const int maxn=1e5+10;

int next[maxn];
int nextval[maxn];

typedef struct{
	char ch[maxn];
	int lenth;
}String;

String S,T;   //母串S,子串T

void getnextval(){
	int i=1,j=0;
	nextval[1]=0;
	while(i<T.lenth){
		if(j==0 || T.ch[i]==T.ch[j]){
			++i,++j;
			if(T.ch[i]!=T.ch[j]) nextval[i]=j;
			else nextval[i]=nextval[j];
		}
		else j=nextval[j];
	}
}

int KMP()
{												//利用模式串T的next函数求T在主串S中的位置
	int i=1,j=1;
	while(i<=S.lenth && j<=T.lenth){			//两个串均未比较到串尾
		if(j==0 || S.ch[i]==T.ch[j])			//继续比较后继字符
			++i,++j;
		else j=nextval[j];							//模式串向右移动
	}
	if(j>T.lenth) return i-T.lenth;			//匹配成功
	else return 0;								//匹配失败
}

int main(){
	int ans;
	memset(next,0,sizeof(next));
	memset(nextval,0,sizeof(nextval));
	scanf("%s %s",S.ch+1,T.ch+1);
	S.lenth=strlen(S.ch+1);
	T.lenth=strlen(T.ch+1);
	getnextval();
	ans=KMP();
	if(!ans) puts("匹配失败");
	else printf("在%d处匹配成功\n",ans);
	return 0;
}

接下来是OJ上的一些练习题:

OJ:HDU 2087:http://acm.hdu.edu.cn/showproblem.php?pid=2087

利用KMP的题解如下:

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

int next[1005];

typedef struct{
	char ch[1005];
	int lenth;
}String;

String S,T;

void getnext(){
	int i=1,j=0;
	next[1]=0;
	while(i<T.lenth){
		if(j==0||T.ch[i]==T.ch[j]){
			++i,++j;
			next[i]=j;
		}
		else j=next[j];
	}
}

int KMP(int ans)
{
	int i=ans,j=1;
	while(i<=S.lenth && j<=T.lenth){
		if(j==0 || S.ch[i]==T.ch[j])
			++i,++j;
		else j=next[j];
	}
	if(j>T.lenth) return i;
	else return -1;
}

int main(){
	int ans;
	while(~scanf("%s",S.ch+1)){
		if(S.ch[1]=='#') break;
		scanf("%s",T.ch+1);
		int cnt=0;
		memset(next,0,sizeof(next));
		S.lenth=strlen(S.ch+1);
		T.lenth=strlen(T.ch+1);
		getnext();
		ans=1;
		while(1){
			ans=KMP(ans);
			if(ans!=-1) cnt++;
			else break;
		}
		printf("%d\n",cnt);
	} 
	return 0;
}

 

HDU 1686:http://acm.hdu.edu.cn/showproblem.php?pid=1686

与北大poj 3461:http://poj.org/problem?id=3461是同一道题

 利用KMP(最长公共前后缀实现)的题解如下:

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;

const int maxn=1e6+10;
const int INF = 0x3f3f3f3f;

int m,n,cnt;
char a[maxn],b[maxn];
int nxt[maxn];

void getnet(){
	int i=1,j=0;
	while(i<m){
		if(b[i]==b[j]) nxt[i++]=++j;
		else if(!j) i++;
		else j=nxt[j-1];
	}
}

int kmp(){
	int i=0,j=0;
	while(i<n){
		if(a[i]==b[j]) i++,j++;
		else if(!j) i++;
		else j=nxt[j-1];
		if(j==m){
			j=nxt[j-1];
			cnt++;
		}
	}
	return cnt;
}

int main(){
	int i,j,t;
	scanf("%d",&t);
	while(t--){
		cnt=0;
		scanf("%s %s",b,a);
		n=strlen(a);
		m=strlen(b);
		getnet();
		printf("%d\n",kmp());
	}
	return 0;
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值