这篇博客基本上是我用来给自己review的。所以大家没必要细看。
这里我强烈推荐别人写的一篇博客。我也是看了它的才恍然大悟。
只不过该博客有一个问题就是他的字符串下表是从1开始的,这令我很不习惯,所以我下面的整理都会基于从0开始的字符串。
一. 算法的基本流程
不介绍基本流程先介绍核心的next数组肯定是一头雾水。所以我打算先介绍基本流程,神秘的kmp算法留到下一节讲。
首先算法也就这么几步:
- 初始化i = 0, j = 0;
- 如果匹配:++i, ++j;
- 如果不匹配:j = next[j]
二. 关于next数组
想必大家也看到了next数组,也很想了解什么是next数组。
所以next数组到底是什么呢?他就是让i不回退只回退j的魔法所在。
具体的说其实:
- next数组储存的是该字符之前字符串从第一个字符开始的前缀和到该字符前最后一个字符结束的后缀的最大公共部分的长度 + 1。
- 所谓的前后缀不能完全重叠,仅此而已。
所以你要求next数组,甚至可以通过双指针来扫描。不过我们还有编写更为简单的迭代法。基本的算法就是:
使用双指针,其中i是扫描整个数组,j是我们next数组的值。
- 如果next[i] = j, 那么next[i + 1] = j + 1;
- 不然,j = next[j];
迭代的基本思路就是利用next的值求更小的前后缀公共部分长度:
三. KMP算法的本质
本质就是目标串中的该字符在模式串的后缀和前缀相同,所以我们下一次直接从next的位置开始查找(请看之前next的定义)
四. 其他问题
- 为什么next[0]要强制设定为-1?
- 首先你要给定一个合理索引范围外的值,这样我们就可以通过判断这个值来停止迭代。
- 如果把next[0]设置为-1, 发现++它就变为0,正好符合我们重新判断后的状态,更重要的是,这使我们匹配和不匹配所做的事情一致了,编写代码更加容易了。
- 不过在与目标串的配对过程中,得额外处理j = -1的情况,看来也不是绝对地便捷啊。
- 关于索引问题?如果传入的是string类型,为了访问其大小,我们得使用size方法,不过该方法返回的是无符号数,如果j=-1且与其比较,很可能会出现问题,因此我建议把<改成!=;
五. 代码实现
#include<bits/stdc++.h>
using namespace std;
void GetNext(string pattern, int next[]) {
// 根据模式串求其next数组
next[0] = -1;
int i = 0, j = -1;
while (i != pattern.size()) {
if (j == -1 || pattern[i] == pattern[j]) next[++i] = ++j;
else j = next[j];
}
}
int Strstr(string target, string pattern) {
int i = 0, j = 0;
int next[100]; // next数组
GetNext(pattern, next);
// 看看是否匹配
// 这里pattern.size的返回值是size_t 与int不一样
while (i != target.size() && j != pattern.size()) {
if (j == -1 || target[i] == pattern[j]) { // 匹配
++i; ++j;
} else {
j = next[j]; // 不匹配
}
}
if (j == pattern.size()) return (i - j);
else return -1;
}
int main() {
string s1, s2;
cin >> s1 >> s2;
cout << Strstr(s1, s2) << endl;
return 0;
}