KMP算法原理原来这么简单

我觉得这句话说的很好:
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,前缀表的过程。 主要有如下三步:

  1. 初始化
  2. 处理前后缀不相同的情况
  3. 处理前后缀相同的情况

初始化:
定义两个指针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:

输入:haystack = “sadbutsad”, needle = “sad”
输出:0
解释:“sad” 在下标 0 和 6 处匹配。
第一个匹配项的下标是 0 ,所以返回 0 。

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;        
    }
};
  • 15
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 12
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

侠客cheems

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值