方法一(有点冗余加难以记忆,建议使用第二种模板)
KMP算法:
P3375 【模板】KMP字符串匹配
文本串:abcab
模式串:abcacababcab
强烈推荐看的kmp视频:KMP算法字符串匹配
KMP算法的next数组推导
#include <stdio.h>
#include <string.h>
char p[1000000];//模式串
char t[1000000]; //文本串
int len1,len2;
int next[1000000];
int next2[1000000];
void getnext() //对文本串自行进行折叠算出next公共前后缀长度
{
int j,len;
len=next[0]=0; //第一个字符本身不具备公共前后缀
j=1; //从第二个字符开始
while(j<len1)
{
if(t[len]==t[j]) //如果当前字符和最长公共前后缀相等,next的值就加1
next[j++]=++len;
else if(len>0) //如果不相等的话就向前定位到最长前后缀的地方
len=next[len-1]; //这里是斜下定位到之前的公共前后缀
else
next[j++]=0; //如果最长公共前后缀长度为0并且还不相等的话就直接赋值0
}
}
void kmp()
{
int i,j;
for(j=len1-1; j>0; j--)
next2[j]=next[j-1]; //这里是将next向后移动一位
next2[0]=-1; //并且将第一个next设置为-1,是为了避免一直查找本身就死循环了
i=j=0;
while(i<len2)
{
if(j==len1-1&&t[j]==p[i]) //这里判断如果文本串到了最后一个还相等的话就找到公共子串的位置了
{
printf("%d\n",i-j+1); //地址从0开始,就加1,表示个数
j=next2[j]; //这里是定位到后缀的开头位置
if(j==-1) //如果越界了的话就定位到第一个字符
j=0;
}
if(t[j]==p[i]) //如果相等的话就两个指针都后移
{
i++;
j++;
}
else
{
j=next2[j]; //如果不相等的话就向前查找后缀的第一个位置
if(j==-1) //如果越界了表面一直向前查不到后缀了的话就将两个指针都后移向后查找
i++;
j++;
}
}
}
}
int main()
{
scanf("%s%s",p,t);
len2=strlen(p);
len1=strlen(t);
getnext();
kmp();
for(int i=0;i<len1;i++)
printf("%d ",next[i]);
}
~~*
删除线格式
方法二:
最近新学的一个简短的KMP算法也特别容易记忆。
题目入口
模式串:abcab
文本串:abcacababcab
一样的,用模式串的最长前缀长度去匹配文本串的最长后缀长度。
变成了这样:
模式串: abcab
文本串:abcacababcab
但有时不光只会有单个字符重复:
模式串:abcabc
文本串:abcabdababcabc
当我们发现在第六位失配时,我们可以将模式串的第一二位移动到第四五位,因为它们相同
模式串: abcabc
文本串:abcabdababcabc
其实就是将模式串的下标j定位到了j这个下标的最长公共前后缀的位置。
下面先看模式串和文本串的匹配过程
cin>>n>>a+1>>m>>b+1; //下标都从1开始
for(int j=0,i=1;i<=m;i++)
{ //这里j从0开始表示第j位已经匹配过了
while(j&&a[j+1]!=b[i])//这里表示如果匹配失败就不断向前跳
j=kmp[j];
if(a[j+1]==b[i])//如果能够匹配了,模式串++
j++;
if(j==n)
{
printf("%d ",i-n);
j=kmp[j];//这里找到了一个匹配地址,但是还要找下一个匹配地址所以也要跳
}
for(int j=0,i=2;i<=n;i++) //i=2是因为下标从1开始,而第一个模式串的值为0,所以直接从2开始
{
while(j&&a[j+1]!=a[i])//如果匹配不成功就向前跳
j=kmp[j];
if(a[j+1]==a[i]) //如果相等,模式串++
j++;
kmp[i]=j; //对模式串赋值
}
在这里我们都是用j=0开始。是因为j=0在界外并且也符合第一个所要匹配的长度为0,i=2开始是因为i=1的kmp[1]的值为0,所以直接从2开始,因为j=0,我们也不能将i==j的情况来匹配(自身和自身匹配永远是相等那样就会死循环),所以用j+1来和i来匹配。
总体思路:
1.i和j+1,看是否匹配,不匹配的话j往前面跳。
2.如果匹配的话j++.
3.判断j有没有匹配到结尾,如果匹配到了结尾就输出开头,并且往前面跳来寻找下一个匹配字符串的开头位置。
总体代码:
#include <iostream>
using namespace std;
const int N=1E5+10,M=1E6+10;
int kmp[N];
char a[N],b[M];
int n,m;
int main()
{
cin>>n>>a+1>>m>>b+1;
for(int j=0,i=2;i<=n;i++)
{
while(j&&a[j+1]!=a[i])
j=kmp[j];
if(a[j+1]==a[i])
j++;
kmp[i]=j;
}
for(int j=0,i=1;i<=m;i++)
{
while(j&&a[j+1]!=b[i])
j=kmp[j];
if(a[j+1]==b[i])
j++;
if(j==n)
{
printf("%d ",i-n);
j=kmp[j];
}
}
}