/*
kmp算法
给定一个模式串p和一个主串s,求模式串(p)在主串(s)中出现的位置,
字符串的下标均从1开始
暴力做法的缺点:浪费了前面已匹配的位置信息 i动,j每次从1开始
kmp算法:i指针不动,j指针回跳到恰当的位置 i指针指着s,j指针指着p
1.取最长的相等前后缀,可以保证不漏解
2.通过模式串前后缀的自我匹配的长度,计算nex函数,
给j指针打一张表,失配时就跳到next[j]的位置继续匹配
next函数
next[i] 表示模式串p[1,i]中相等前后缀的最长长度
a a b a a b a a a a
ne[1]=0 a a
ne[2]=1 aa a|a 1
ne[3]=0 aab a|b aa|ab 2
ne[4]=1 aaba a|a aa|ba aab|aba 3
ne[5]=2 aabaa a|a aa|aa aab|baa aaba|abaa 4
ne[6]=3 aabaab a|b aa|ab aab|aab 5
ne[7]=4 aabaaba aaba|aaba 6
ne[8]=5 aabaabaa aabaa|aabaa 7
ne[9]=2 aabaabaaa aab|aaa aa|aa aaba|baaa 8
ne[10]=2 aabaabaaaa aa|aa 9
前i个字符,枚举i-1次
暴力枚举的次数 1~n-1 时间复杂度o(n*n)
增加有规律,在前一个基础上最多增加1
模拟:
i=2 j=0 p[2]==p[1] j=1 ne[2]=1
i=3,j=1 while(1&&p[3]!=p[2]) j=ne[3]=0; ne[3]=0
i=4,j=0 p[4]==p[1] j=1 ne[4]=1
i=5,j=1 p[5]==p[2] j=2 ne[5]=2
i=6,j=2 p[6]==p[3] j=3 ne[6]=3
i=7,j=3 p[7]==p[4] j=4 ne[7]=4
i=8,j=4 p[8]==p[5] j=5 ne[8]=5
i=9,j=5 while(5&&p[9]!=p[6]) 匹配失败,回跳,j=0
p[9]=p[1] j=1
p[9]=p[2] j=2;
p[9]!=p[3] ne[9]=2;
i=10,j=2 while(2&&p[10]!=p[3]) j=0;
p[10]=p[1] j=1
p[10]=p[2] j=2
p[10]!=p[3] ne[10]=2
代码:
ne[1]=0;
for(int i=2,j=0;i<=n;i++)
{
while(j&&p[i]!=p[j+1])
j=ne[i];
if(p[i]==p[j+1])
j++;
ne[i]=j;
}
双指针: i扫描模式串p,j扫描前缀
初始化,ne[1]=0,i=2,j=0;
每轮for循环,i向右走一步
1.如果p【i】!=p【j+1】 让j回跳到可以匹配的位置,
如果找不到匹配的位置,j回跳到0
2.如果p[i]==p【j+1】,让j+1,指向匹配前缀的末尾
3.ne[i]=j的值(更新)
j指针所走的总步数决定了总的执行次数。每轮for,j至多+1,
那么j一共向右至多走n步,往左跳的步数加起来不会超过n步,
否则j变为负数,所以j的总步数不会超过2*n步,所以时间复杂度为o(n)
s c a a b a a b a a b a a b a a a a b
p a a b a a b a a a a
ne 0 1 0 1 2 3 4 5 2 2
模拟
i=1 j=0 while(j&&s[1]!=p[1]) j=ne[0]=0;
i=2 j=0 s[2]=p[1] j=1<n
i=3 j=1 s[3]=p[2] j=2
i=4 j=2 s[4]=p[3] j=3
i=5,j=3 s[5]=p[4] j=4
.....
i=9 j=7 s[9]=p[8] j=8
i=10,j=8 s[10]!=p[9] j=ne[8]=5
s[10]==p[6] j=6<n
i=11,j=6 s[11]== p[7] j=7
i=12,j=7 s[12]== p[8] j=8
i=13,j=8 s[13]!=p[9] j=ne[8]=5
s[13]==p[6] j=6
i=14,j=6 s[14]==p[7] j=7
i=15,j=7, s[15]==p[8] j=8
i=16,j=8 s[16]==p[9] j=9
i=17,j=9 s[17]==p[10] j=10; 17-10+1=8
模式串与主串匹配
for(int i=1,j=0;i<=m;i++)
{
while(j&&s[i]!=p[j+1]) j=ne[j];
if(s[i]==p[j+1]) j++;
if(j===n) printf("%d\n",i-n+1);
}
双指针,i扫描主串,j扫描模式串
初始化,i=1,j=0;
每轮for,i向右走一步。
1. 如果s[i]!=p[j+1] ,让j回跳到能匹配的位置 ,如果找不到该位置,j回跳到0
2. 如果s[i]==p[j+1] ,让j向右走一步
3. 若匹配成功,输出匹配位置
时间复杂度o(m+n);
*/
#include<bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;
const int inf=0x3f3f3f3f;
const int N=1e6+10;
int n,m;
int ne[N];
string zhu,fu;
void getnext()
{
ne[1]=0;
int i,j;
for(i=2,j=0;i<=n;i++)
{
while(j&&fu[i]!=fu[j+1]) j=ne[i];
if(fu[i]==fu[j+1]) j++;
ne[i]=j;
}
}
void kmp()
{
int i,j;
for(i=1,j=0;i<=m;i++)
{
while(j&&zhu[i]!=fu[j+1]) j=ne[j];
if(zhu[i]==fu[j+1]) j++;
if(j==n)
{
printf("%d ",i-n);//小标从0开始 i-n+1
}
}
}
void solve()
{
cin>>n;
cin>>(fu+1);
cin>>m;
cin>>(zhu+1);
getnext();
kmp();
}
signed main()
{
int t=1;
while(t--)
{
solve();
}
return 0;
}