给定一个字符串 text 和一个模式串 pattern,求 pattern 在text 中的出现次数。text 和 pattern 中的字符均为英语大写字母或小写字母。text中不同位置出现的pattern 可重叠。
输入格式:
输入共两行,分别是字符串text 和模式串pattern。
输出格式:
输出一个整数,表示 pattern 在 text 中的出现次数
输入样例1:
zyzyzyz
zyz
输出样例1:
AABAACAADAABAABA
AABA
输出:
3
数据范围与提示:
1≤text, pattern 的长度 ≤106, text、pattern 仅包含大小写字母。
分析思路
笔者刚开始学习KMP算法时看了很多文章,教程还是不太懂,现在总算是弄懂了。
为了避免朴素算法的低效,所以前辈们设计出了一个模式匹配算法,可以大大避免重复遍历的情况即KMP算法
我认为KMP算法的关键在于对要查找的字符串在比较前先做一个分析,减少查找难度。遍历分析这个字符串,得到当前字符串的前后缀相似度,将其保存到一个数组中, 即next数组。
得到当前字符串的前后缀相似度的目的是:在主串和匹配子串遍历到不匹配时,从头回溯,可以从当前不匹配的位置i,的第next[i]处进行查找。
123456789
例如主串:abcababca
子串:abcabx
当遍历到第六个a不等于x时,由于前后缀相似度x前的4.5.位ab等于前1.2位ab(子串4.5位匹配主串4.5位,即1.2.位也与主串4.5位匹配)所以回溯成主串的第6位与子串第三位比较
子串回溯的位置就由next数组中的前后缀相似度决定
void kmp(){
int i=0,j=0;
while(j<t.size()) //遍历主串
{
if(t[j]==s[i]||i==-1){//如果主串与子串当前位置匹配,或为子串第一个位置
i++; //子串向后一位
j++; //主串向后一位
}
else {
i=Next[i]; //不匹配,去找子串中当前位置对应next数组中存放的值即另一个子串位置是否与主串匹配
}
if(i==s.size()){//匹配完成
ans++; //匹配完成次数+1
}
}
}
那要怎么得出next数组呢
见详细注释
void getNext()
{
int j =0;//表示后缀位置
int k =-1;//表示前缀遍历到的位置
Next[0]=-1; //第一位标记为0
while(j < s.size()) //遍历子串
{
if(k ==-1|| s[j]== s[k]) //当前子串是初始值位置或前后缀相同
{
j++; //后缀位置+1
k++; //前缀匹配到的位置+1
Next[j]= k;
}
else
{
k =Next[k]; //不同从存放的next数组中找当前前缀位置的前缀开始比较
//可以理解为使前缀短一点比较看看
}
}
}
完整代码含主函数
#include<bits/stdc++.h>
using namespace std;
int Next[100001],ans=0;
string s,t;
void getNext()
{
int j =0;//表示后缀位置
int k =-1;//表示前缀遍历到的位置
Next[0]=-1; //第一位标记为0
while(j < s.size()) //遍历子串
{
if(k ==-1|| s[j]== s[k]) //当前子串是初始值位置或前后缀相同
{
j++; //后缀位置+1
k++; //前缀匹配到的位置+1
Next[j]= k;
}
else
{
k =Next[k]; //不同从存放的next数组中找当前前缀位置的前缀开始比较
//可以理解为使前缀短一点比较看看
}
}
}
void kmp(){
int i=0,j=0;
while(j<t.size()) //遍历主串
{
if(t[j]==s[i]||i==-1){//如果主串与子串当前位置匹配,或为子串第一个位置
i++; //子串向后一位
j++; //主串向后一位
}
else {
i=Next[i]; //不匹配,去找子串中当前位置对应next数组中存放的值即另一个子串位置是否与主串匹配
}
if(i==s.size()){//匹配完成
ans++; //匹配完成次数+1
}
}
}
int main ()
{
cin>>t>>s;
getNext();
kmp();
printf("%d",ans);
return 0;
}