KMP(字符串查询匹配)

原文

代码预览

题目来源:P3375 【模板】KMP字符串匹配

#include <cstdio>
#include <cstring>

char p[1000005], t[1000005];
int len1, len2;
int next[1000005], next2[1000005];

void do_next()
{
    next[0] = 0;
    int i = 1;
    int len = 0;

    while (i < len2)
    {
        if (t[i] == t[len])
            next[i++] = ++len;
        else
        {
            if (len > 0)
                len = next[len - 1];
            else
                next[i++] = len;
        }
    }
}

void kmp()
{
    int i = 0, j = 0;
    next2[0] = -1;
    
    for (int i = 1; i < len2; i++)
        next2[i] = next[i - 1];
    
    while (i < len1)
    {
        if (j == len2 - 1 && p[i] == t[j])
        {
            printf("%d\n", i - j + 1);
            j = next2[j];
            if (j == -1)
                j++;
        }
        if (p[i] == t[j])
        {
            j++;
            i++;
        }
        else
        {
            j = next2[j];
            if (j == -1)
            {
                i++;
                j++;
            }
        }
    }
}

int main()
{
    scanf("%s%s", p, t);
    len1 = strlen(p);
    len2 = strlen(t);

    do_next();

    kmp();

    for (int i = 0; i < len2; i++)
        printf("%d ", next[i]);

    return 0;
}

推导详解

假设我们的匹配字串为: P:ABABABCABAB T:ABABC

前后缀推导

首先我们需要请出一个炒鸡有名的表格!

子串长度子串前后缀长度(小于子串长度)
1A0
2AB0
3A B A1
4AB AB2
5ABABC0

我们从上到下把前后缀长度拿下来,就是next数组啦!

是不是非常简单!!

算法详解及代码实现

首先,我们需要一个变量len来表示此时的前后缀长度,以及变量i来对t字串进行遍历。 因为前后缀长度小于子串长度,所以我们的i直接从1开始,也就是:

i = 1;
len = 0;

接下来,我们来思考一个简单的问题。

当我们子串长度为3时,子串为A B A, 此时len = 1。这一步结束后,len++,len=2。

那么,当我们将子串长度增加至4时,我们如何判断能不能组成更长的(len为2的)前后缀呢? 只要判断增加的那一位(即当前子串最后一位,由i指向),与len指向的那一位是否相同即可。也就是只需要判断 ABA B 这一位与 A B AB 这一位是否相等即可。这里很明显是相等的,所以前后缀长度从1增加至2。

这一部分对应的代码为:

if (t[i] == t[len])
    next[i++] = ++len;

(对应上方16-17行)

那如果不相同呢?比如当子串长度为5(ABABC)的时候? 此时的前后缀长度为0,因为突然多出了一个C,不管怎么弄都不可能匹配成功嘛。 所以咱一顺手就写上了:

len = 0;

我们看看最上面是怎么写的:

if (len > 0)
    len = next[len - 1];
else
    next[i++] = len;

(对应上方20-23行)

emmmmmm……相差好像有点大? 俗话说:事出反常必有妖。让我们来仔细研究下。

打个比方,我们此时的子串和next数组分别如下:

A B A B C A B A A
0 0 1 2 0 1 2 3 ?

此时len=3,而len指向的那一位是B,子串末位是A,两者不同,所以这里应该写0——诶诶诶慢着!好像有哪里不对!! 哦!托马斯·陈独秀先生,我的上帝!让我们仔细瞧瞧:这里的前后缀明明都是A啊!!!前后缀长度应该是1不是0啊!!!! 因此,我们需要对算法进行修正,此时的前后缀长度,应为

len = next[len - 1]

许多教程将这一步称之为“回溯”

这就完了?!不要忘了,len - 1可是下标,小心下标越界! 于是我们需要加一个判断以避免该情况的发生。

if (len > 0)
    len = next[len - 1];

那万一这个时候len=0怎么办呢? 那不就说明这个时候的前后缀长度为0嘛!这一位的next就等于0啊! 最后别忘了i++!

next[i++] = len;

至此,整段next推导代码分析完毕,再回过头看看整段代码:

void do_next()
{
    next[0] = 0;
    int i = 1;
    int len = 0;

    while (i < len2)
    {
        if (t[i] == t[len])
            next[i++] = ++len;
        else
        {
            if (len > 0)
                len = next[len - 1];
            else
                next[i++] = len;
        }
    }
}

全文完

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值