扩展kmp学习

拓展kmp是对KMP算法的扩展,它解决如下问题:

定义母串S,和字串T,设S的长度为n,T的长度为m,求T与S的每一个后缀的最长公共前缀,也就是说,设extend数组,extend[i]表示T与S[i,n-1]的最长公共前缀,要求出所有extend[i](0<=i<n)。

扩展KMP求的是对于原串S1的每一个后缀子串与模式串S2的最长公共前缀。它有一个next[]数组和一个extend[]数组。

next[i]表示为模式串S2中以i为起点的后缀字符串和模式串S2的最长公共前缀长度.

注意到,如果有一个位置extend[i]=m,则表示T在S中出现,而且是在位置i出现,这就是标准的KMP问题,所以说拓展kmp是对KMP算法的扩展,所以一般将它称为扩展KMP算法。

下面举一个例子,S=”aaaabaa”,T=”aaaaa”,首先,计算extend[0]时,需要进行5次匹配,直到发生失配。

从而得知extend[0]=4,下面计算extend[1],在计算extend[1]时,是否还需要像计算extend[0]时从头开始匹配呢?答案是否定的,因为通过计算extend[0]=4,从而可以得出S[0,3]=T[0,3],进一步可以得到 S[1,3]=T[1,3],计算extend[1]时,事实上是从S[1]开始匹配,设辅助数组next[i]表示T[i,m-1]和T的最长公共前缀长度。在这个例子中,next[1]=4,即T[0,3]=T[1,4],进一步得到T[1,3]=T[0,2],所以S[1,3]=T[0,2],所以在计算extend[1]时,通过extend[0]的计算,已经知道S[1,3]=T[0,2],所以前面3个字符已经不需要匹配,直接匹配S[4]和T[3]即可,这时一次就发生失配,所以extend[1]=3。

通过上面的算法可知,我们原串S1的每一个字符串只会进行一次匹配,extend[k+1]的计算可以通过之前extend[i](0<=i<=k)的值得出,由于需要用相同的方法对模式串S2进行一次预处理,所以扩展KMP的时间复杂度为O(l1+l2),其中,l1为原串S1的长度,l2为模式串S2的长度。

模版:

const int maxn=100010;   //字符串长度最大值
int next[maxn],ex[maxn]; //ex数组即为extend数组
//预处理计算next数组
void GETNEXT(char *str) {
	int i=0,j,po,len=strlen(str);
	next[0]=len;//初始化next[0]
	while(str[i]==str[i+1]&&i+1<len)//计算next[1]
		i++;
	next[1]=i;
	po=1;//初始化po的位置
	for(i=2; i<len; i++) {
		if(next[i-po]+i<next[po]+po)//第一种情况,可以直接得到next[i]的值
			next[i]=next[i-po];
		else { //第二种情况,要继续匹配才能得到next[i]的值
			j=next[po]+po-i;
			if(j<0)j=0;//如果i>po+next[po],则要从头开始匹配
			while(i+j<len&&str[j]==str[j+i])//计算next[i]
				j++;
			next[i]=j;
			po=i;//更新po的位置
		}
	}
}
//计算extend数组
void EXKMP(char *s1,char *s2) {
	int i=0,j,po,len=strlen(s1),l2=strlen(s2);
	GETNEXT(s2);//计算子串的next数组
	while(s1[i]==s2[i]&&i<l2&&i<len)//计算ex[0]
		i++;
	ex[0]=i;
	po=0;//初始化po的位置
	for(i=1; i<len; i++) {
		if(next[i-po]+i<ex[po]+po)//第一种情况,直接可以得到ex[i]的值
			ex[i]=next[i-po];
		else { //第二种情况,要继续匹配才能得到ex[i]的值
			j=ex[po]+po-i;
			if(j<0)j=0;//如果i>ex[po]+po则要从头开始匹配
			while(i+j<len&&j<l2&&s1[j+i]==s2[j])//计算ex[i]
				j++;
			ex[i]=j;
			po=i;//更新po的位置
		}
	}
}

例题 hdu-2328

Corporate Identity

 

题意:给你n个字符串,求这n个字符串的最长公共子串

思路:有多种方面可以做出这道题,我们这里先找出最短的一个母串,然后枚举它的每一个子串,对于每一个子串和原来的母串进行扩展KMP匹配,然后记录匹配的最大值和对应的位置即可,需要注意的时,多个答案时,输出字典序最小的子串。

//#include<bits/stdc++.h>
/*
next[0]=l2; 
next[i]=max{ k|i<=i+k-1<l2 &&str.substring(i,i+k-1) == str.substring(0,k-1) } 其中str.substring(i, j)表示str从位置i到位置j的子串,如果i>j则,substring为空。
next[i]表示为以模式串s2中以i为起点的后缀字符串和模式串s2的最长公共前缀长度.
extend[i]表示为以字串s1中以i为起点的后缀字符串和模式串s2的最长公共前缀长度.
*/
#include<iostream>
#include<algorithm>
#include<cstring>
#include<string>
#include<map>
using namespace std;
const int maxn=100010;   //字符串长度最大值
const int INF=int(1e9);
int nxt[maxn],ex[maxn]; //ex数组即为extend数组
char s1[maxn],s2[maxn];
char s[5010][210];
//预处理计算next数组
void get_next(char *str) {
	int i=0,j,po,len=strlen(str);
	nxt[0]=len;//初始化next[0]
	while(str[i]==str[i+1]&&i+1<len)//计算next[1]
		i++;
	nxt[1]=i;
	po=1;//初始化po的位置
	for(i=2; i<len; i++) {
		if(nxt[i-po]+i<nxt[po]+po)//第一种情况,可以直接得到next[i]的值
			nxt[i]=nxt[i-po];
		else { //第二种情况,要继续匹配才能得到next[i]的值
			j=nxt[po]+po-i;
			if(j<0)j=0;//如果i>po+next[po],则要从头开始匹配
			while(i+j<len&&str[j]==str[j+i])//计算next[i]
				j++;
			nxt[i]=j;
			po=i;//更新po的位置
		}
	}
}
//计算extend数组
bool exkmp(char *s1,char *s2) {
	int i=0,j,po,len=strlen(s1),l2=strlen(s2);
	get_next(s2);//计算子串的next数组
	while(s1[i]==s2[i]&&i<l2&&i<len)//计算ex[0]
		i++;
	ex[0]=i;
	po=0;//初始化po的位置
	if(ex[0]==l2)
		return true;
	for(i=1; i<len; i++) {
		if(nxt[i-po]+i<ex[po]+po)//第一种情况,直接可以得到ex[i]的值
			ex[i]=nxt[i-po];
		else { //第二种情况,要继续匹配才能得到ex[i]的值
			j=ex[po]+po-i;
			if(j<0)j=0;//如果i>ex[po]+po则要从头开始匹配
			while(i+j<len&&j<l2&&s1[j+i]==s2[j])//计算ex[i]
				j++;
			ex[i]=j;
			po=i;//更新po的位置
		}
		if(ex[i]==l2)
			return true;
	}
	return false;
}
void char_(char *str1,char *str,int l,int r) {    //将str字符串的(l,r)赋值给str1 
	for(int i=l; i<=r; i++) {
		str1[i-l]=str[i];
	}
	str1[r-l+1]='\0';
	return ;
}
int main() {
	int n;
	while(~scanf("%d",&n)&&n) {
		scanf("%d",&n);
		int Min=INF,pos;
		for(int i=1; i<=n; i++) {
			scanf("%s",s[i]);
			if(strlen(s[i])<Min) {    //找出最短母串 
				Min=strlen(s[i]);
				pos=i;
			}
		}
		int Max=0;
		int l=strlen(s[pos]);
		int left,right;
		for(int i=0; i<l; i++) {   //枚举母串的每一个子串 
			for(int j=i; j<l; j++) {
				if((j-i+1)<Max) continue;   //剪枝 
				char_(s1,s[pos],i,j);
				int flag=true;
				for(int z=1; z<=n; z++) {  //每一个子串和原来的母串进行匹配 
					if(z==pos) continue;
					if(exkmp(s[z],s1))
						continue;
					flag=false;
					break;
				}
				if(flag) {
					if(j-i+1==Max){      //记录所有母串的最长公共子串,长度相同的情况下记录字典序最小的子串 
						for(int z=0;z+left<=right;z++){
							if(s[pos][z+left]<s[pos][z+i])
							break;
							else{
								if(s[pos][z+left]>s[pos][z+i]){
									left=i;
									right=j;
									break;
								}
							}
						}
					}
					if(j-i+1>Max) {
						left=i;
						right=j;
						Max=max(Max,j-i+1);
					}
				}
			}
		}
		if(Max==0)
			printf("IDENTITY LOST\n");
		else {
			for(int i=left; i<=right; i++) {
				printf("%c",s[pos][i]);
			}
			printf("\n");
		}
	}
	return 0;
}


--------------------- 
参考链接:https://blog.youkuaiyun.com/qq_40160605/article/details/80407554

参考链接:https://segmentfault.com/a/1190000008663857

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值