前言:字符串匹配问题,学了很久的kmp ,一直没有学会,老实反复忘记,emmm,于是近几天又重新学习,似乎此次比之前要好很多。
大家可以先看看这个视频,会好理解一点。
大家可能不理解的是nxt 数组 怎么求,我们通过视频可以了解到,假设 我们的模式串是这个“abcdabca”
按照视频的算法 他的nxt数组是从下标0开始的, 大部分博主的kmp算法都是从下标为1开始的,从acwing查询模板 是找这个样子
个人认为,记得住哪个,就记哪个。一定要理解,当然 ,你能背下来另外说了。
回归主题:视频中的kmp 求nxt数组,是自己跟自己比较,求出最大的前缀和后缀匹配的长度。
当然最大不可以是自己。比如说 aaaaa 那么就是 0 1 2 3 4 最后一个是4 而不是 5。 我们这里以下标为0 开始。
以这个图为例、 从视频得知,我们这里令 假设字符串长度为n 设置两个双指针 ,i = 0 ,j = 1,kmp 的本质 是 一个指针永远不会回退,这样我们一个指针 扫过的长度 永远是字符串长度。时间复杂度就是O(n) 。
全局设置nxt[N] 数组 默认值为0
那么 i = 0 j = 1 如果 s[i] == s[j] 那么对应的nxt[j] = i + 1;
为什么要等于i + 1 因为前面如果匹配了,加上这个字母匹配,是不是要加1.所以nxt[j] =i + 1;
i 表示前面有几个 字母相匹配。
大家到这里肯定很蒙蔽 orz 一切的前提请结合视频 再来观看。
然后 i++ j++
如果不匹配 那么 i 就回退到 上一个匹配到的下标 i = nxt[i-1] 我们是 索引是0 开始 所以 无需像 下标为1 开始一样 加1 。 直接用就是了。
回退什么时候停止呢? 一直回退到两个字符相等 或者 i = 0 为止。
下面我们模拟一遍。
i = 0 j = 1 发现两者不相等且i 不等于0 的时候 ,i 回退到 nxt[i-1]。 否则无需回退。如若回退, 我们得知 i= nxt[0] = 0 那么 回退停止 ,比较两者是否相等,相等则是 nxt[j] = i + 1 否则 nxt[j] = i 。
j 从1 到 3 都是这样 直到 j = 4 我们发现 s[i] == s[j] 那么j++ i++ nxt[4] = i + 1;
同理nxt[5] = 2 nxt[6] = 3 当J = 7时候 发现 s[3] != s[7] 所以 i 回退 nxt[i-1] = nxt[2] = 0
比较s[0] 和s[7] 相等 则是 i + 1 。从始至终 j 没有回退过,所以时间复杂度是O(n)。
至此nxt数组 大致求法就结束了 下面贴出自己写的代码
void nx() {
for (int i = 0, j = 1; j < s.size(); j++) {
if (s[i] == s[j]) {
nxt[j] = i + 1;
i++;
} else {
while (s[i] != s[j] && i) {
i = nxt[i - 1];
}
if (s[i] == s[j]) {
nxt[j] = i + 1;
i++;
} else {
nxt[j] = i;
i = 0;
}
// 17 -22 简洁 变成
// if(s[i] = s[j] j++
// nxt[j] = i
}
}
}
kmp 算法与此也类似。现在模式串不是和自己比较了 而是跟文本串比较。
大致代码贴如下
void kmp(string s ,string t){
int ss = s.size();
int tt = t.size();
for(int i = 0 ,j = 0;i<ss;i++){
// 默认ss 是text tt 是pattern
while(s[i] != p[j] && j ){
j = nxt[j-1];
}
if(s[i] == p[j]){
j++;
}
// 主要是注意顺序
if(j == tt){
cout <<i-tt + 1<<" ";
j =nxt[j-1];
}
}
}
完整代码如下
#include <bits/stdc++.h>
using namespace std;
int nxt[100];
string s,p;
void nx() {
for (int i = 0, j = 1; j < s.size(); j++) {
if (s[i] == s[j]) {
nxt[j] = i + 1;
i++;
} else {
while (s[i] != s[j] && i) {
i = nxt[i - 1];
}
if (s[i] == s[j]) {
nxt[j] = i + 1;
i++;
} else {
nxt[j] = i;
i = 0;
}
// 17 -22 简洁 变成
// if(s[i] = s[j] j++
// nxt[j] = i
}
}
}
void kmp(string s ,string t){
int ss = s.size();
int tt = t.size();
for(int i = 0 ,j = 0;i<ss;i++){
// 默认ss 是text tt 是pattern
while(s[i] != p[j] && j ){
j = nxt[j-1];
}
if(s[i] == p[j]){
j++;
}
// 主要是注意顺序
if(j == tt){
cout <<i-tt + 1<<" ";
j =nxt[j-1];
}
}
}
void clearConsole() {
#ifdef _WIN32
system("cls");
#else
system("clear");
#endif
}
int main() {
// while (1) {
// memset(nxt, 0, sizeof(nxt));
// cin >> s;
// cin.ignore(); // 忽略之前输入的回车
// nx();
// for (int i = 0; i < s.size(); i++) {
// cout << nxt[i] << " ";
// }
// cout << endl;
// cin.get(); // 等待用户按下回车
// system("cls");
// }
cin>>s>>p;
nx();
kmp(s,p);
return 0;
}
最后: 大家一定要看视频,要有自己的思考,写代码,并不是说抄就可以了,你抄这么多代码,并没有什么用,emm 反而会陷入一种过拟合的状态,你只会做你知道的,但是没见过的,但是算法其实都讲过,你还是不会写。就很尴尬了,然后回望自己写的文章,发现emm一言难尽,并不是书面化的表达。个人能力有限,仍需多加学习。