我觉得这句话说的很好:
kmp算法关键在于:在当前对文本串和模式串检索的过程中,若出现了不匹配,如何充分利用已经匹配的部分,来继续接下来的检索。
暴力解决字符串匹配
暴力解法就是两层for循环,每次都一对一的匹配,如果匹配错误就文本串开始位置加1,继续下一次
前缀表的作用
前缀表的作用就是当前位置匹配失败之后,通过前缀表记录的数字来去找到模式串中最合适的位置去重新开始下一次匹配,也就是说,匹配到当前位置失败,通过前缀表跳到前面位置继续匹配。
我们一个一个匹配,如果出现不匹配,这时候我们希望通过已经匹配过的字符串,找到其相同的前缀与后缀,来继续接下来的匹配。
那我们该如何去计算这个前缀字串与后缀字串在每个位置的最大值,来去继续模式串匹配呢?就是接下来的前缀表的计算(也就是很多KMP算法中next数组的计算)
前缀表的计算(next数组计算)
- 什么是前缀?
前缀就是一个字符串不包括最末尾的字符从第一个开始的所有连续字串
主串:aabaac
前缀:a 、aa 、aab 、aaba 、aabaa - 什么是后缀?
后缀就是一个字符串不包含第一个字符,每次以末尾结尾的连续字串
主串:aabaac
后缀:c 、ac 、aac 、baac 、abaac - 如何计算前缀表?
如何使用前缀表?
在KMP算法中,一般的next数组会直接使用前缀表,也可能在前缀表的基础上改进一点,但是最终的思路都是围绕前缀表展开,比如有些实现方法是把前缀表统一减一或者整体右移一位,初始位为-1。
时间复杂度:主串与模式串匹配过程最多为O(N),模式串的next数组生成为O(M),所以最后的时间复杂度可以为O(N+M),相较于暴力写法的O(N*M),KMP算法还是极大的提高了检索的效率。
构造next数组
构造next数组其实就是计算模式串s,前缀表的过程。 主要有如下三步:
- 初始化
- 处理前后缀不相同的情况
- 处理前后缀相同的情况
初始化:
定义两个指针i和j,j指向前缀末尾位置,i指向后缀末尾位置。
然后还要对next数组进行初始化赋值,如下:
int j = -1;
next[0] = j;
处理前后缀不相同的情况:
因为j初始化为-1,那么i就从1开始,进行s[i] 与 s[j+1]的比较。
所以遍历模式串s的循环下标i 要从 1开始,代码如下:
for (int i = 1; i < s.size(); i++) {
如果 s[i] 与 s[j+1]不相同,也就是遇到 前后缀末尾不相同的情况,就要向前回退。next[j]就是记录着j(包括j)之前的子串的相同前后缀的长度。那么 s[i] 与 s[j+1] 不相同,就要找 j+1前一个元素在next数组里的值(就是next[j])。
回退代码:
while (j >= 0 && s[i] != s[j + 1]) { // 前后缀不相同了
j = next[j]; // 向前回退
}
处理前后缀相同的情况
如果 s[i] 与 s[j + 1] 相同,那么就同时向后移动i 和j 说明找到了相同的前后缀,同时还要将j(前缀的长度)赋给next[i], 因为next[i]要记录相同前后缀的长度。
if (s[i] == s[j + 1]) { // 找到相同的前后缀
j++;
}
next[i] = j;
构建next数组最终代码:
void getNext(int* next, const string& s){
int j = -1;
next[0] = j;
for(int i = 1; i < s.size(); i++) { // 注意i从1开始
while (j >= 0 && s[i] != s[j + 1]) { // 前后缀不相同了
j = next[j]; // 向前回退
}
if (s[i] == s[j + 1]) { // 找到相同的前后缀
j++;
}
next[i] = j; // 将j(前缀的长度)赋给next[i]
}
}
本文主要是看了代码随想录的有感理解+画图🚀🚀
KMP算法题目:leetcode -28
28.找出字符串中第一个匹配项的下标
给你两个字符串 haystack 和 needle ,请你在 haystack 字符串中找出 needle 字符串的第一个匹配项的下标(下标从 0 开始)。如果 needle 不是 haystack 的一部分,则返回 -1 。
示例 1:文章来源:https://www.toymoban.com/news/detail-420640.html
输入:haystack = “sadbutsad”, needle = “sad”
输出:0
解释:“sad” 在下标 0 和 6 处匹配。
第一个匹配项的下标是 0 ,所以返回 0 。文章来源地址https://www.toymoban.com/news/detail-420640.html
class Solution {
public:
void Getnext(string s, vector<int>& next)
{
int j=0;
next[j]=0;
for(int i=1;i<next.size();i++)
{
while(j>0 && s[i]!=s[j])
{
j=next[j-1];
}
if(s[i]==s[j])
{
j++;
}
next[i]=j;
}
}
int strStr(string haystack, string needle) {
vector<int> next(needle.size(),0);
Getnext(needle,next);
int j=0;
for(int i=0;i<haystack.size();i++)
{
while(j>0 && haystack[i] != needle[j])
{
j=next[j-1];
}
if(haystack[i]==needle[j])
{
j++;
}
if(j==needle.size())
{
return i-needle.size()+1;
}
}
return -1;
}
};
到了这里,关于KMP算法原理原来这么简单的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!