KMP算法

9 篇文章 0 订阅
1 篇文章 0 订阅

KMP参考网址

学了数据结构,突然想起自己KMP还没有整理

1.问题提出

假设我们现在面临这样一个问题:有一个文本串S和一个模式串T,现在要查找P在S中的位置,该如何查找呢?

2.BF(暴力匹配法)

2.1思想

从主串的第一个字符开始和模式T的第一个字符进行比较。若相等,则继续比较两者的后续字符;否则,从主创S的第二个字符开始和模式T的第一个字符进行比较。重复上述过程,直至S或T中所有字符比较完毕。若T中的字符全部比较完毕,则匹配成功,返回本趟匹配的开始位置;否则匹配失效,返回0。
时间复杂度:最好情况时为O(m+n);最坏情况为O(m*n)
伪代码就不写了,直接上代码~

2.2代码
int BF(char S[],char T[]){
	int start=0; 
	int i=0,j=0;
	while((S[i]!='\0')&&(T[j]!='\0')){
		if(S[i]==T[j]){
			i++,j++;
		}else{
			start++;
			i=start;
			j=0;
		}
	} 
	if(T[j]=='\0')
		return start+1;
	return 0;
}

 

3.KMP算法

3.1定义

Knuth-Morris-Pratt 字符串查找算法,简称为 “KMP算法”,常用于在一个文本串S内查找一个模式串P 的出现位置,这个算法由Donald Knuth、Vaughan Pratt、James H. Morris三人于1977年联合发表,故取这3人的姓氏命名此算法

3.2解释

  BF算法中,一旦S和T匹配失败后,T的下标就会回溯,有些回溯是不必要的,因此,希望某趟在S和T匹配是白虎,下标i不回溯,下标j回溯至某个位置k,使得T[k]对准S[i]继续进行比较。显然,关键问题是如何确定位置k。
  观察部分匹配成功时的特征,某趟在S[i]和T[j]匹配失败后,下一趟比较从S[i]和T[k]开始,则有T[0] ~ T[k-1] = S[i-k] ~ S[i-1]成立

在部分匹配成功时,有T[j-k] ~ T[j-1] = S[i-k] ~S[i-1]
在这里插入图片描述
有上面两个式子结合可知

T[0] ~ T[k-1] = T[j-k] ~ T[j-1]

该式子说明,模式中的每一个字符T[j]都对应一个k值,这个k值仅依赖于模式本身,与主串无关。用next[j]表示T[j]对应的k值(0<=j<m)其定义如下:

next数组解析如下
① 蓝色框之前为abc,没有前缀和后缀相同的字符,所以为0
② 绿色框之前虽然有ab这个字符串是重复的,但是abcaabbc的前缀和后缀是a和c,相等的串里边要包含前缀和后缀

求next数组代码

void getNext(char p[],int next[]){
	next[0]=-1;
	int i=0,j=-1;
	while(i<strlen(p)){
		if(j==-1||p[i]==p[j]){
			i++;
			j++;
			next[i]=j;
		}
		else
			j=next[j];
	}
}

 

3.3伪代码

在这里插入图片描述


int kmp(char *a,char *b){
	int i=0,j=0;
	getNext(b);
	for(int k=0;k<strlen(b);k++)
        cout<<next[k];
    int len1=strlen(a),len2=strlen(b);
	while(i<len1&&j<len2){
		if(j==-1||a[i]==b[j]){
			i++;j++;
		}
		else
			j=next[j];
	}
	if(j==strlen(b))
		return i-j;
	return -1;
}

这里出现了个小问题:kmp函数中while(i<len1&&j<len2)不能写成while(i<strlen(a)&&j<strlen(b))
strlen返回的是一个size_t类型,size_t类型是unsigned int,unsigned int 和int比较,int会强转成高级的unsigned int。计算机里面使用补码保存数据,所以j = -1时转成unsigned int 0xffffffff

4.经典例题

Number Sequence

此题可水,不贴代码

Oulipo
#include <bits/stdc++.h>
using namespace std; 
int next[1000100];
int sum=0;
void getNext(char *ptr){
	int i=0,j=-1;
	next[0]=-1;
	while(i<strlen(ptr)){
		if(j==-1||ptr[i]==ptr[j]){
			i++;j++;
			next[i]=j;
		}
		else
			j=next[j];
	}
}

void kmp(char *a,char *b){
	int i=0,j=0;
	getNext(b);
	int len1=strlen(a),len2=strlen(b);
	while(i<len1&&j<len2){
		if(j==-1||a[i]==b[j]){
			i++;j++;
		}
		else
			j=next[j];
		if(j==len2){
				sum++;
				j=next[j];
			}
	}
	
}

char a[1000100],b[1000100];
int main(){
	int t;
	cin>>t;
	while(t--){
		sum=0;
		cin>>b>>a;
		memset(next,0,sizeof(next));
		kmp(a,b);
		cout<<sum<<endl;
	}
	return 0;
} 

 

Seek the Name, Seek the Fame

题解
给一个字符串S, 求出所有前缀pre,使得这个前缀也正好是S的后缀。 输出所有前缀的结束位置。

例如 “ababcababababcabab”, 以下这些前缀也同时是S的后缀

ab : 位置2

abab : 位置4

ababcabab : 位置9

ababcababababcabab : 位置 18


  如图所示,根据next[ ]前缀的性质左边有颜色部分和右边有颜色部分完全相等
  因为左边的红色和右边的红色相等,如果左边的红色和左边的绿色相等,则左边的红色必定与右边的绿色相等
  既左红+左蓝+左绿和左绿是我们要求的子串
根据这个规律不断地递推下去,所有的子串都可以求出来

代码

#include <bits/stdc++.h>
using namespace std; 
int next[400010];
int sum=0;
void getNext(char *ptr){
	int i=0,j=-1;
	next[0]=-1;
	int len=strlen(ptr);//这里一定要记住用len 不然会TLE
	while(i<len){
		if(j==-1||ptr[i]==ptr[j]){
			i++;j++;
			next[i]=j;
		}
		else
			j=next[j];
	}
}


char a[400010];
int main(){
	while(cin>>a){
		int newNext[400010];
		memset(next,0,sizeof(next));
		getNext(a);
		int cnt=0;
		int len=strlen(a);
		newNext[0]=len;
		int k=next[len];
		while(k>0){
			newNext[++cnt]=k;
			k=next[k];
		}
		for(int i=cnt;i>=0;i--){
			cout<<newNext[i];
			if(i!=0)
				cout<<" ";
		}
		cout<<endl;
	}
	return 0;
} 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值