KMP的简单理解


前言

看完很多讲解KMP算法的博客和视频,还是依旧懵懵懂懂,随后我根据代码反推了一遍过程才大致了解了KMP的实现过程。


提示:以下是本篇文章正文内容,仅是个人的理解,方便今后的复习。第一次尝试写博客,能力有限,敬请雅正。

一、得到next数组

我们都知道KMP算法中,难以理解的就是找到next数组,我分为以下两点进行理解。

1.为什么要找最长公共前后缀?

前后缀的概念

一个字符串的前缀是从第一个字符开始的连续若干个字符,例如ABCAB,有4个前缀,分别是

AABABCABCA

有4个后缀,分别是

BABCABBCAB

则该字符串的最长公共前后缀为AB

举一个简单的例子,假设主串为:
在这里插入图片描述
模式串为:
在这里插入图片描述

将主串与模式串进行匹配,发现到此处不相匹配,而此时模式串的最长公共前后缀即为两个方框部分。
在这里插入图片描述

如果我们移动模式串,我们会发现,在前缀部分移动到后缀部分之前,模式串是不可能与主串相匹配的。
所以我们进行下一步匹配时,直接将前缀部分移动到后缀部分即可。
在这里插入图片描述


2.如何找最长公共前后缀,得到next数组

因为next数组是针对模式串的移动的,所以要从模式串中找最长公共前后缀
先上代码(示例):

//p为模式串,ne为next数组
void get_Next()
{
    int i = 0, j = -1;
    ne[0] = -1;
    //n为模式串的长度
    while (i < n)
    {
        if (j == -1 || p[i] == p[j])
        {
            i ++, j ++;
            ne[i] = j;
        }
        else
            j = ne[j];
    }
}

因为要找不匹配点前的最长公共前后缀,定义i,j两个指针,让next[0] = -1,初始状态如下

在这里插入图片描述
通过上述代码一步步推演,得出next的值
在这里插入图片描述
当j != -1 ,并且p[i]!=p[j]时,j = next[0] = -1
在这里插入图片描述
接一下来继续按照程序一步步推演即可
在这里插入图片描述
在这里插入图片描述
此时p[i] == p[j] 即 i++, j++
在这里插入图片描述
到最后得到next数组
在这里插入图片描述


二、得到next数组之后进行匹配

依旧先上代码(示例):

void KMP_search()
{
	//主串和模式串皆从0开始
	//定义i为主串指针,j为模式串指针
    int i = 0, j = 0;
    //m为主串的长度
    while (i < m)
    {
        if (j == -1 || s[i] == p[j])
            i ++, j ++ ;
        else
            j = ne[j];
        if(j == n)
            printf("%d ", i - j);
    }
}

提示:i为主串小标,j为模式串的下标,next数组表示模式串的移动
开始匹配到此处停止
在这里插入图片描述
移动模式串,即将模式串前缀部分移动到后缀部分,此时i = 5, j = 5, next[5] = 2
在这里插入图片描述
此时依旧不匹配,继续移动模式串,next[2] = 0

此时next[0] = -1,i,j同时++

此处开始匹配一直到最后匹配成功!

接下来附上一道acwing上的模板题链接:KMP字符串


三、字符串循环节

对于某一字符串S[1~i],在它众多的next[i]的“候选项”中,
如果存在某一个next[i],
使得: i % (i−next[i])==0 ,
那么 S[1~ (i−next[i])] 可以为 S[1 ~ i] 的循环节,
而 i/(i−next[i])即是它的循环次数 K

假设字符串长度为len,那么最小循环节的长度为len - ne[len]


证明过程感兴趣的话可以自行百度。
接下来附上一道关于KMP求循环节的题目:周期

题解代码如下:

#include <iostream>
#include <string>
#include <algorithm>
#include <vector>
#include <stack>
#include <queue>
#include <deque>
#include <unordered_map>
using namespace std;
typedef long long  ll;
typedef pair<int, int> P;
const int N = 1e5 + 6, M = 1e6 + 6;
const int inf = 0x3f3f3f3f;
int n, ne[M], cnt = 1;
char s[M];
void get_next()
{
    int i = 0, j = -1;
    ne[0] = -1;
    while (i < n)
    {
        if (j == -1 || s[i] == s[j])
        {
            i ++, j ++;
            ne[i] = j;
        }
        else
            j = ne[j];
    }
}
int main()
{
    while (scanf("%d", &n), n != 0)
    {
        scanf("%s", s);
        get_next();
        printf("Test case #%d\n", cnt ++);
        for (int i = 2; i <= n ; i ++ )
        {
        	//根据题目输出样例要求这个next数组的值不能为0
            if (ne[i] && i % (i - ne[i]) == 0)
                printf("%d %d\n", i, i / (i - ne[i]));
        }
        printf("\n");
    }
    return 0;
}

总结

KMP算法,只看描述的话比较抽象也难以理解,个人觉得结合着算法代码和样例进行反推会更容易理解。

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值