KMP算法详解 理论+代码

此篇博客将KMP算法在字符串匹配中的应用。

例:文本串:aabaabaaf
模式串:aabaaf

1️⃣ 理论篇

正常的匹配思路,对文本串的每一个位置,往右遍历模式串长度,若匹配则返回,不匹配则继续文本串下一个位置,复杂度 O ( m n ) O(mn) O(mn)

KMP算法:设置双指针,分别指向文本串和模式串的其中一个位置,文本串指针永不回溯,当不匹配时回溯模式串指针,复杂度 O ( n ) O(n) O(n)

在讲解KMP算法前,先介绍什么是前缀和后缀,对于字符串aab,前缀:a,aa,后缀:b,ab
我们定义这样一个结构:next数组,用来记录一个字符串中最长相等前后缀的长度
这个定义较为抽象,我们来介绍例子,对于串a,只有一个字符,定义为0;对于串aa,值为1;对于串aab,没有相等的前后缀,为0;对于aabaa,值为2.

根据上述工作,我们可以求出模式串每个前缀的最长相等前后缀长度,并得到next数组这么一个结构。
在实际匹配中,到第6个字符我们会发现不匹配,此时看模式串前缀aabaa,得到前缀数组中的值为2,之后便可让模式串指针从第6变成第3,继续匹配,可以有效减少回溯次数。

2️⃣ 代码篇
先说下next数组的变种,对于上面的模式串,next数组应该为0,1,0,1,2,0
第一个变种会右移一位(最后一个用不到),第一个变成-1,这样遇见冲突时不用比较上一位,直接查找冲突为的next值即可。
第二个变成会让每个数-1,这种不太常见。

本次代码采取next数组第一个变种。

接下来我们来讲解如何在 O ( N ) O(N) O(N)时间复杂度内求next数组
正常的做法,即我们的做法肯定是对每个前缀,遍历得到值,但这样是 O ( N 2 ) O(N^2) O(N2)的时间复杂度。

设置i,j指针分别代表前缀的最后一个字符位置和后缀最后一个字符位置,同时i也是next数组的最后一个元素(可以思考一下为什么)
开始时,前缀不存在,所以i = -1,初始化next数组第一个元素是-1,j指向第一个元素。
由于i指向-1,所以无法匹配,0加入next数组,并让i等于0,j指向下一个元素。
此时就开始匹配了,如果i和j对应出的字符相同,说明可以由上一个i+1得到j处的next值。
如果不相同,就令i = next[i],这一步是最难思考的,i是下标i之前的最长相等前后缀长度,next[i]也是下标next[i]之前的最长相等前后缀长度。这两个是怎么结合在一起的呢?我们可以从next[i]的定义出发,从i到next[i]可以保证前后缀仍然有相等元素,只是长度会变短。理解了这个就懂了。
上面的理解还不够细,准确的说,如果不匹配,那么当前前后缀是相同的,找更短的最长相等前后缀找其中一个子串即可,一定是和找整个串等价。

而在匹配部分的代码和计算next数组是完全一致的,不同之处是前缀结尾i应该初始化为0,后缀结尾j也是0.

此处代码是leetcode 28题代码

class Solution {
public:
    int strStr(string haystack, string needle) {
        int n = needle.size();
        if(n == 0)return 0;
        int next[n];
        memset(next, -1, sizeof(next));

        int i = -1, j = 0; // i和j代表前缀最后位置和后缀最后位置
        while(j<n-1)
        {
            if(i < 0 || needle[j] == needle[i])
            {
                i++;
                j++;
                next[j] = i;
            }
            else i = next[i];
        }

        // 根据next数据进行匹配
        i = 0, j = 0;
        while(j<haystack.size())
        {
            if(i<0 || haystack[j] == needle[i])
            {
                i++;
                j++;
                if(i == n)return j-n;
            }
            else i = next[i];
        }

        return -1;
    }
};
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值