我对KMP算法的简单理解
前言:字符串匹配问题
问题概述:
“字符串A是否为字符串B的子串?如果是,出现在B的什么位置?”这个问题就是字符串匹配问题。字符串A称为模式串(zs
),字符串B称为主串(ss
)。
其中,解决字符串匹配问题有两种方法:
- 暴力求解(朴素模式匹配算法)
- KMP算法
朴素模式匹配算法
思路
从主串的第一个字符开始与模式串的第一个字符进行比较,如果相同则模式串指针和主串的指针都往下走一位,再去进行比较。如果相同,则主串指针向下移动一位,模式串的指针回到初始的地方,重新开始进行比较。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
// 暴力求得子串
int re_index(string ss,string sz){
int len_s = ss.length();
int len_z = sz.length();
int i = 0;
int j = 0;
while(i<len_s&&j<len_z){
if(ss[i]==sz[j]){
i++; j++;
}else{
i = i - j + 1; //i要从开始的地方+1再进行向后遍历
j = 0;
}
}
if(j==len_z){
return i-j; // 回到i刚刚开始的地方
}else{
return -1;
}
}
int main()
{
string ss,sz;
cin>>ss>>sz;
cout<<re_index(ss,sz)<<endl;
return 0;
}
KMP算法
KMP算法可以先读一下这两篇文章:
什么是KMP算法(详解)
可能是全网最清晰的KMP算法讲解
之后,KMP算法就可以分为两个部分:
K M P 算法 = 求 n e x t 数组 + 遍历求下标 KMP算法 = 求next数组 + 遍历求下标 KMP算法=求next数组+遍历求下标
求next数组 == 求最长的公共前后缀
-
公共前后缀
一个字符串的前n个字符和后n个字符,分别是它的前缀和后缀,如果它的前缀和后缀相同,则它们的长度就是公共前后缀的长度。(⚠️:在这个情景中,单个字符的最长公共前后缀的长度为0,不是1)
-
next数组
next[i]
表示为:在前 $ i $ 位 ( 0 ~ i − 1 ) (0~i-1) (0~i−1)的字符串中的最长公共前后缀的长度。- 比如: aba 的字符串中的最长公共前后缀的长度为 1 1 1({a})
- 比如:abcab的最长公共前后缀的长度为 2 2 2 {ab}
KMP算法的核心是:当某个位置匹配失败时,移动到这个位置之前的字符串的最长前缀的下一个字符继续匹配。而寻找这个位置的任务就交给next数组来完成
-
求next数组
将问题转化为:求最长的公共前后缀问题
这个问题我感觉像是一个dp问题next[0] = -1 // 默认初始化为-1,因为当第一位置就匹配失败时,两个坐标都要向前进行+1操作
next[1] = 0 // 单个字符的最长公共前后缀的长度为0
next[i]
的情况: 表示的是前i位的字符串中最长公共前后缀长度,也就是说next[i]
中存储的数字是最长公共前缀的最后一个字符的下一个字符的坐标(在代码中一直用k来进行代替)sz[next[i]]等价于sz[k]
表示的是前i位的字符串中最长公共前缀串的下一个字符。而sz[i]
恰恰表示的是该段子串的最后一个字符- 当
sz[i]==sz[k]
也就是最长前缀的后一个字符与该段子串的最后一个字符相匹配时,就要将最长公共前后缀的长度去进行+1操作 - 如果
sz[i]!=sz[k]
,则该段子串的最长公共前后缀的长度仍然和之前长度相同,k不变。
- 当
// 求特定模式串(子串)的next数组
vector<int> re_next(string zs){
int len = zs.length();
vector<int> next(len,0);
int k = -1;
int i = 0;
while(i<len){
if(k==-1||zs[k]==zs[i]){
next[i] = k;
i++;k++;
}else{
next[i++] = k;
}
}
return next;
}
求index坐标
求index的坐标基本就不难理解了
//求坐标
int re_index(string ss,string zs,vector<int> next){
int len_s = ss.length();
int len_z = zs.length();
int i,j;
i = 0;
j = 0;
while(j<len_z&&i<len_s){
if(j==-1||ss[i]==zs[j]){
i++;
j++;
}else{
j = next[j];
}
}
if(j==len_z){
return i - j;
}
else{
return -1;
}
}
可以借助于视频来进行理解一下:
完整代码
/*---------------------------------------------------------------
Proverb : Make a little progress every day
AUthor : programmerxing
Question : 1
Data : 2023 - 01 - 29
Time : 23:21:22
----------------------------------------------------------------*/
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
// KMP算法
vector<int> re_next(string zs){
int len = zs.length();
vector<int> next(len,0);
int k = -1;
int i = 0;
while(i<len){
if(k==-1||zs[k]==zs[i]){
next[i] = k;
i++;k++;
}else{
next[i++] = k;
}
}
return next;
}
int re_index(string ss,string zs,vector<int> next){
int len_s = ss.length();
int len_z = zs.length();
int i,j;
i = 0;
j = 0;
while(j<len_z&&i<len_s){
if(j==-1||ss[i]==zs[j]){
i++;
j++;
}else{
j = next[j];
}
}
if(j==len_z){
return i - j;
}
else{
return -1;
}
}
int main()
{
string zs,ss;
cin>>ss>>zs;
vector<int> next = re_next(zs);
for(int e : next){
cout<<e<<' ';
}
cout<<endl;
cout<<re_index(ss,zs,next)<<endl;
return 0;
}