(http://www.elijahqi.win/2017/07/07/%E6%B4%9B%E8%B0%B73375/)
题目描述
如题,给出两个字符串s1和s2,其中s2为s1的子串,求出s2在s1中所有出现的位置。
为了减少骗分的情况,接下来还要输出子串的前缀数组next。如果你不知道这是什么意思也不要问,去百度搜[kmp算法]学习一下就知道了。
输入输出格式
输入格式:
第一行为一个字符串,即为s1(仅包含大写字母)
第二行为一个字符串,即为s2(仅包含大写字母)
输出格式:
若干行,每行包含一个整数,表示s2在s1中出现的位置
接下来1行,包括length(s2)个整数,表示前缀数组next[i]的值。
输入输出样例
输入样例#1:
ABABABC ABA
输出样例#1:
1 3 0 0 1
说明
时空限制:1000ms,128M
数据规模:
设s1长度为N,s2长度为M
对于30%的数据:N<=15,M<=5
对于70%的数据:N<=10000,M<=100
对于100%的数据:N<=1000000,M<=1000
样例说明:
所以两个匹配位置为1和3,输出1、3
题解:我用了扩展kmp和kmp的结合
套用exkmp 最后写出next数组【kmp的next】(部分匹配值)求解过程 输出
套用exkmp 最后写出next数组【kmp的next】(部分匹配值)求解过程 输出
“部分匹配值”就是”前缀”和”后缀”的最长的共有元素的长度。以”ABCDABD”为例,
- “A”的前缀和后缀都为空集,共有元素的长度为0;
- “AB”的前缀为[A],后缀为[B],共有元素的长度为0;
- “ABC”的前缀为[A, AB],后缀为[BC, C],共有元素的长度0;
- “ABCD”的前缀为[A, AB, ABC],后缀为[BCD, CD, D],共有元素的长度为0;
- “ABCDA”的前缀为[A, AB, ABC, ABCD],后缀为[BCDA, CDA, DA, A],共有元素为”A”,长度为1;
- “ABCDAB”的前缀为[A, AB, ABC, ABCD, ABCDA],后缀为[BCDAB, CDAB, DAB, AB, B],共有元素为”AB”,长度为2;
- “ABCDABD”的前缀为[A, AB, ABC, ABCD, ABCDA, ABCDAB],后缀为[BCDABD, CDABD, DABD, ABD, BD, D],共有元素的长度为0。
(部分匹配值)求解思路
通过上文完全可以对kmp算法的原理有个清晰的了解,那么下一步就是编程实现了,其中最重要的就是如何根据待匹配的模版字符串求出对应每一位的最大相同前后缀的长度。我先给出我的代码:
void makeNext(const char P[],int next[])
{
int q,k;//q:模版字符串下标;k:最大前后缀长度
int m = strlen(P);//模版字符串长度
next[0] = 0;//模版字符串的第一个字符的最大前后缀长度为0
for (q = 1,k = 0; q < m; ++q)//for循环,从第二个字符开始,依次计算每一个字符对应的next值
{
while(k > 0 && P[q] != P[k])//递归的求出P[0]···P[q]的最大的相同的前后缀长度k
k = next[k-1]; //不理解没关系看下面的分析,这个while循环是整段代码的精髓所在,确实不好理解
if (P[q] == P[k])//如果相等,那么最大相同前后缀长度加1
{
k++;
}
next[q] = k;
}
}
现在我着重讲解一下while循环所做的工作:
已知前一步计算时最大相同的前后缀长度为k(k>0),即P[0]···P[k-1];
此时比较第k项P[k]与P[q],如图1所示
如果P[K]等于P[q],那么很简单跳出while循环;
关键!关键有木有!关键如果不等呢???那么我们应该利用已经得到的next[0]···next[k-1]来求P[0]···P[k-1]这个子串中最大相同前后缀,可能有同学要问了——为什么要求P[0]···P[k-1]的最大相同前后缀呢???是啊!为什么呢? 原因在于P[k]已经和P[q]失配了,而且P[q-k] ··· P[q-1]又与P[0] ···P[k-1]相同,看来P[0]···P[k-1]这么长的子串是用不了了,那么我要找个同样也是P[0]打头、P[k-1]结尾的子串即P[0]···Pj-1,看看它的下一项P[j]是否能和P[q]匹配。如图2所示
#include <cstdio>
#include <cstring>
#define N1 1100000
#define N2 1100
char S[N1],T[N2];
int e[N1],next[N2],L1,L2;
void getnext(){
next[0]=L2;
for (int i=1;i<L2;++i) {
if (T[i]==T[i-1]) next[1]++;else break;
}
int a=1,p=next[1]-1;
for (int i=2;i<L2;++i){
int l1,l2;
l1=next[i-a];l2=p-i+1;
if (l1<l2) next[i]=l1;else{
int j=l2;
if (j<0) j=0;
while (i+j<L2&&T[i+j]==T[j])++j;
next[i]=j;p=i+j-1;a=i;
}
}
}
void getextend(){
for (int i=0;i<L1;++i){
if (i<L2&&S[i]==T[i]) e[0]++;else break;
}
int p=e[0]-1,a=0;
for (int i=1;i<L1;++i){
int l1=next[i-a],l2=p-i+1;
if (l1<l2) e[i]=l1;else{
int j=l2;
if (j<0) j=0;
while (j<L2&&i+j<L1&&S[i+j]==T[j])++j;
e[i]=j;p=i+j-1;a=i;
}
}
}
void makenext(){
next[0]=0;//q:模版字符串下标;k:最大前后缀长度
for (int q=1,k=0;q<L2;q++){
while (k>0&&T[q]!=T[k]) k=next[k-1];
if (T[q]==T[k]){
++k;
}
next[q]=k;
}
}
int main(){
freopen("3375.in","r",stdin);
freopen("3375.out","w",stdout);
scanf("%s%s",S,T);
L1=strlen(S);L2=strlen(T);
getnext();
getextend();memset(next,0,sizeof(next));
makenext();
//for (int i=0;i<=L2;++i) printf("%d ",next[i]);printf("\n");
//for (int i=0;i<L1;++i) printf("%d ",e[i]);
for (int i=0;i<L1;++i) if (e[i]>=L2) printf("%d\n",i+1);
for (int i=0;i<L2;++i) printf("%d ",next[i]);
return 0;
}
真正的kmp
#pragma GCC optimize (2)
#include<cstdio>
#include<cstring>
#define N 1100000
char s1[N],s2[N];
int nxt[N],ans[N],tot;
int main(){
// freopen("3375.in","r",stdin);
scanf("%s",s1+1);scanf("%s",s2+1);nxt[1]=0;
int n=strlen(s2+1);
for (int i=2,k=0;i<=n;++i){
while(k&&s2[i]!=s2[k+1]) k=nxt[k];
if (s2[i]==s2[k+1]) ++k;
nxt[i]=k;
}int nn=n;
n=strlen(s1+1);
for (int i=1,k=0;i<=n;++i){
while (k&&s1[i]!=s2[k+1]) k=nxt[k];
if (s1[i]==s2[k+1]) ++k;
if (k==nn) ans[++tot]=i-nn+1;
}
for (int i=1;i<=tot;++i) printf("%d\n",ans[i]);
for (int i=1;i<=nn;++i) printf("%d ",nxt[i]);
return 0;
}