P3375 【模板】KMP - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)https://www.luogu.com.cn/problem/P3375
1.KMP算法的目的
KMP算法用于求在一个较长的母串中是否有你想要的字串
也就相当于Office 软件中的 ctrl + F功能
该题目我用KMP的两种不同理解方式做(本质是一样的)
2.第一种KMP的理解方法
P3375
#include<bits/stdc++.h>
#define MAXN 1000010
using namespace std;
int kmp[MAXN];
int ls1,ls2,j;
int main(){
string s1,s2;
//s1 s2的输入
cin>>s1;
cin>>s2;
ls1=s1.size();
ls2=s2.size();
s1=' '+s1;
s2=' '+s2;
//构造kmp数组
//j指向下标最长匹配真前后缀的长度对应的字符
for(int i=2;i<=ls2;i++){
//不相等的话就逐步向前找次长的匹配真前后缀长度
while(j!=0&&s2[i]!=s2[j+1]){
//不相等的话就逐步向前找 次长的匹配真前后缀长度kmp'[i-1]
//不符合的话继续找kmp''[i-1],以此类推
//每次j都指向前kmp[i-1]个字符的下一位
//找到后 kmp[j]赋给j(相当于继续向前找)
j = kmp[j];
}
//如果相等的话j+1 因为目前j指向的是下标
//+1后才代表的在此指向的字符前面的字串的长度
if(s2[i] == s2[j+1]) j++;
//得出kmp的长度 因为每次j都指向前kmp[i-1]个字符的下一位
//所以对应的kmp[i]就是j+1后代表的数字
kmp[i]=j;
}//构造完毕
//使用kmp数组在主串上移动模式串
j=0;//一会还用j当指针 先初始化为0一下
for(int i=1;i<=ls1;i++){
//j+1的原因是 找下标为kmp[j]的下一位
//拿它和模式串上的作比较
while(j>0&&s1[i]!=s2[j+1]){
//不相等的话,缩短范围 找次长的kmp值
//即次长真匹配前后缀的长度,以此类推
j = kmp[j];
}
//谐音 kmp 看门牌
if(s1[i]==s2[j+1]){
j++;//kmp[j]之前的已经默认比对完
//开始比对下一位
}
if(j==ls2){
//j==ls2 说明已经找到啦
cout<<i-ls2+1<<endl;
//i代表最后一个匹配上的字符位置
//ls2代表模式串长度,减去是为了找开头
//+1是为了抵消索引偏移
j = kmp[j];
//继续查找
}
}
for(int i = 1;i<=ls2;i++){
cout<<kmp[i]<<" ";
}
return 0;
}
3.第二种KMP理解方法
推荐个B站博主的视频讲解,可视化讲解很清晰明了
//P3375
#include <bits/stdc++.h>
using namespace std;
/*
最长匹配真前后缀的长度(pi值)
即某个字符串的前缀和后缀子串相等时的最大长度
举个例子 ATAATA 的前缀有
下标 0 1 2 3 4 5
字符串 A T A A T A
前缀 A AT ATA ATAA ATAAT ATAATA(下标所代表的字母之前的串)
pi值 0 0 1 1 2 3
对于 ATAA 当前后缀都为A时 pi值最大 1
对于 ATAAT 当前后缀都为AT时 pi值最大 2
对于 ATAATA 当前后缀都为ATA时 pi值最大 3
模式串 ATAATA
主串 AGCATAATAATTAA
将它们用一个特殊字符拼接
合并串 A T A A T A # A G C A T A A T A A T T A A
pi值 0 0 1 1 2 3 / 1 0 0 1 2 3 4 5 6 4 5 0 1 1
* * * * * * * * * * * ↑ 和模式串的长度相等
pi值的定义就是当前点(包括本身)向前数pi个数的字符串(后缀)
和前pi个数的字符串(前缀)相同 如上所示 找到了第一次重复的位置
*/
int main(){
string s1,s2,str;
//s1 s2的输入
cin>>s1;
cin>>s2;
int m =s2.size();
//s1 s2 拼接
str= s2+'#'+s1;
//构建前缀函数pi
vector<int>pi(str.size(),0);
for(int i=1;i<(int)str.size();i++){
int len =pi[i-1];
while(len!=0&&str[i] != str[len]){
len=pi[len-1];
}
if(str[i] == str[len]){
pi[i]=len+1;
//开始判断
if(pi[i]==m){
cout<<i-m*2 +1<<endl;
}
}
}
for(int i=0;i<m;i++){
cout<<pi[i]<<" ";
}
return 0;
}
正如代码中注释所示 pi代表的是最长真匹配前后缀的长度
先用一个母串和子串中都没有的字符将两个串连接起来,设某个点的pi值为Π,那么从这点开始向前数Π个数构成的字符串(后缀)与从开头向后数Π个数构成的字符串(前缀)相同,即说明在母串中找到字串位置
!!!:注意输入字符串时,用cin,不要用getline,因为getline分不清 \n 和\r,差距如下
一个更简单的题目来练手
P1308 [NOIP2011 普及组] 统计单词数 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)https://www.luogu.com.cn/problem/P1308这题也可以用KMP做(题解里好像没人用,有点小题大做的意思,但是挺好用,很容易就可以过)
//P1308 [NOIP2011 普及组] 统计单词数
//KMP做法
#include <bits/stdc++.h>
using namespace std;
int kmp[1000020];
int main(){
string str,str_1;
string apd;
//输入子字符串
getline(cin,str_1);
//输入母字符串
getline(cin,str);
//都转化为小写
for(int i=0;i<(int)str.length();i++){
str[i]=towlower(str[i]);
}
for(int i=0;i<(int)str_1.length();i++){
str_1[i]=towlower(str_1[i]);
}
str_1 = " "+str_1+" ";
str = " "+str+" ";
apd=str_1+"#"+str;
int l = str_1.size();
int sum = 0;//出现的次数
int pos = -1;//第一次出现的位置
for(int i=1;i<apd.size();i++){
int len = kmp[i-1];
while(len!=0&&apd[i]!=apd[len]){
len = kmp[len-1];
}
if(apd[i]==apd[len]){
kmp[i]=len+1;
if(kmp[i]==l){
sum++;
if(sum==1){
pos=i-l*2;
}
}
}
}
if(sum!=0){
cout<<sum<<" "<<pos;
}else{
cout<<"-1";
}
return 0;
}
对于该简单练手题目,再提供一种解法
//P1308 [NOIP2011 普及组] 统计单词数
#include <bits/stdc++.h>
using namespace std;
int main(){
string str,str_1;
//输入子字符串
getline(cin,str_1);
//输入母字符串
getline(cin,str);
//都转化为小写
for(int i=0;i<(int)str.length();i++){
str[i]=towlower(str[i]);
}
for(int i=0;i<(int)str_1.length();i++){
str_1[i]=towlower(str_1[i]);
}
//不包含第一个单词是目标单词的情况
str_1=" "+str_1+" ";
str=" "+str+" ";
int count=0;
int pos;
pos=str.find(str_1);
int first=pos;
if(pos==-1){
cout<<-1;
return 0;
}
else {
while(pos+str_1.length()<str.length()){
if(pos!=-1){
count++;
}else {
break;
}
pos=str.find(str_1,pos+1);
}
}
cout<<count<<" "<<first<<endl;
return 0;
}
运用了C++中的find函数
在C++中,find
函数是算法头文件 <algorithm>
中定义的一个函数,它可以用于在指定范围内查找特定值的元素。以下是find
函数的基本用法:
template <class InputIterator, class T>
InputIterator find(InputIterator first, InputIterator last, const T& value);
参数说明
InputIterator first
:指向要搜索的范围的开始位置的迭代器。InputIterator last
:指向要搜索的范围的结束位置的后一个位置的迭代器。const T& value
:要搜索的值。
返回值
- 如果找到值为
value
的元素,则返回一个指向该元素的迭代器。 - 如果未找到该值,则返回一个指向
last
的迭代器。