拓展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
题意:给你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