KMP随笔 + 模板

本文详细介绍了KMP字符串匹配算法的工作原理及其实现过程。包括如何构建next数组以提高搜索效率,以及如何利用这些信息来实现高效的字符串匹配。文章还提供了三种不同的实现方式,包括使用char类型数组和string类型的示例代码。

Update:我这口胡的是什么啊=-=我都看不懂了 算了算了给个链接这个讲的特好

2024.12.03 Upd:首先观看这个小视频,它除了匹配失败其他讲的都很好,看到六分钟差不多了。

KMP其实最主要的就是如何求next数组。首先理解前缀、后缀、部分匹配值的概念后,我们应该已经知道了这个过程是从模式串T的 从头开始的子串开始逐一增加长度以对应next数组的每一位的。我们首先要想到,这个过程就和主串S匹配模式串T几乎一样,只不过这个时候的“主串”是T,其“模式串”是从T开头逐渐拉长的T的子串,相当于自己和自己的越来越大部分匹配。如果能理解到这层面,那再做一点思考应该自己就能明白了。大部分人其实就是这部分没理清楚,具体来说就是:

Q:我知道有了next数组后就能进行那个什么什么KMP的主串里匹配模式串了,但是我的next数组怎么求?

A:你都知道怎么匹配了,你求next数组的时候你已经有next数组的next数组了,用next数组的next数组求出next数组你不就得到next数组了?(指求第i位的时候i-1位next数组已经求出来了,这部分数组就是求next用到的,而且next[1]=0迟早全都推得出)

Q:?神金

如果不明白就可以继续看。

如何利用前一个状态推导当前状态呢?我们很容易发现当前状态如果变长了,那就一定是上一位的部分匹配小串串的i位后面一位匹配上了。比如ABCAB已经匹配到了长度为2的AB串,那么ABCAB?的?对应前缀AB后的C时才会增加。

除此之外就是匹配失败的部分匹配值如何倒退了。匹配失败时,目前是有长为length的部分已经匹配上,由于不能继续往后接着匹配,所以匹配好的后缀已经没用了,我们关心的是前缀。前缀由于匹配不上所以需要回退,回退的流程就和ST串匹配时一样。但此时我们仅需利用已经匹配好的 前面部分的next数组就能进行回退,让此时的部分匹配串变成部分匹配串内的部分匹配串,然后此时我们就可以通过新的这个小串重新与当前主串第i位重复上一自然段中的那种对最新一位比较情况,就这样一直通过next数组找T后缀和此时T子串前缀的部分匹配串匹配,最后变成最小的空串与第i位比较仍然不成功,就触发大保底next[i]=0了。

以下是很久以前写的,本人也看不懂,有需要直接看例题和代码即可。

-----------------------------------------------分割线-----------------------------------------------

来讲讲KMP

First......读入两字符串后 将将被搜索的关键字 先进行自我配对

            长度1..2..3......len(串) - 1        意为 字符串前x个字符的 前缀 与 后缀 相等的最大长度

            此处注意 前缀 和 后缀 各自的长度 似乎不能等于原串的样子=-=

            每个长度都要保存下来 可存到某一数组里 通常用 next fail 等名称定义 依据是他的作用

Fa♂Q : 这个解释模模糊糊看起来怪怪的存长度的数组...... 有什么用

Ans~ : 之后查找长度的时候会用到~

            作用是 当两个字符串比着比着 突然对不上了的时候

            他就可以挺♂身而出 给将被搜索的关键字跳回去 至于总串嘛他太懒了懒得改

            那关键字怎么改呢

Second...说起来是改 实际上是跳

            首先 你可以通过存两个字符串正在比的地方(用int一类 或 指针) 找到 关键字比到哪儿了

            For instance 以 char[1] 开始 设总串指针为 i 关键字指针为 j

            关键字比到第666位了 然后第667位不一样 则 i = 666

            先设该666位字符是abcab......abcabd 红色的是第667个

            此时总串比到2333位 是......abcabcabc 红色的是第2334个 j = 2333

            哈 通过next[667] = 5 你找到了关键字前666位的前缀和后缀 正是abcab

            因此直接跳把关键字的指针 跳到 2 此时 i = 2

            此时比较关键字第 3 位 c 和 总串第2334位 c 相等

            LOLOLOL 我们就可以开心地比下去啦

            如果此时两字符还不同 通过 next 数组 找到 next[i] 继续跳 直到跳到头 i = 0 就相当于重新匹配了=-= 从零开始的搜索......

Useless notes:

            用 string 读的字符串 和 下标0开头的 char 类型的 KMP 部分几乎是一样的

            然后 下标1 开头1 的 char 又 有所改变=-=

            为防止思路混乱 在此将三篇都记下来(前两者转换只用改改 类型 和 串长求法 而已)

            其中1下标char跑得非常非常慢,但是下标换成0就没事了 原因不详 实在是太毒瘤了

模板题 洛谷P3375

Tip:因为懒得打子程序直接堆一起啦~

sky_killed(被天杀的) char[1] 开头——

#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;
const int MAX = 1 << 21;
char i[MAX],j[MAX];
int next[MAX];
int main()
{
    int b;
    scanf("%s%s",i + 1,j + 1);
    int li = strlen(i + 1);
    int lj = strlen(j + 1);
        for (int a = 2; a <= lj; a++)//建立next数组
        {
            while (b && j[a] != j[b + 1]) b = next[b];
            if (j[a] == j[b + 1]) ++b;
            next[a] = b;
        }
    b = 0;
        for (int a = 1; a <= li; a++)//开始匹配
        {
            while (b > 0 && i[a] != j[b + 1]) b = next[b];
            if (i[a] == j[b + 1]) ++b;
            if (b == lj) printf("%d\n",a - b + 1),b = next[b];
        }
        for (int a = 1; a <= strlen(j + 1); a++) printf("%d ",next[a]);//烦人的题目要求输出next数组=-=
    return 0;
}

总共 T 三个点~

normal char[0] 开头——

#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;
const int MAX = 1 << 21;
char i[MAX],j[MAX];
int next[MAX];
int main()
{
    int b;
    scanf("%s%s",i,j);
    int li = strlen(i);
    int lj = strlen(j);
        for (int a = 1; a < lj; a++)//建立next数组
        {
            while (b && j[a] != j[b]) b = next[b];
            if (j[a] == j[b]) ++b;
            next[a + 1] = b;
        }
    b = 0;
        for (int a = 0; a < li; a++)//开始匹配
        {
            while (b && i[a] != j[b]) b = next[b];
            if (i[a] == j[b]) ++b;
            if (b == lj) printf("%d\n",a - b + 2),b = next[b];
        }
        for (int a = 1; a <= lj; a++) printf("%d ",next[a]);
    return 0;
}

总 300ms~

Useful string ——

Tip:AC自动机要用string哦 char慢死=-=

      好吧也可能是我自己的问题(就是你自己的问题不要推卸责任)

#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;
int next[1 << 21];
int main()
{
    int b;
    string i,j;
    cin >> i;
    cin >> j;
    int li = i.size();
    int lj = j.size();
        for (int a = 1; a < lj; a++)//建立next数组
        {
            while (b && j[a] != j[b]) b = next[b];
            if (j[a] == j[b]) ++b;
            next[a + 1] = b;
        }
    b = 0;
        for (int a = 0; a < li; a++)//开始匹配
        {
            while (b && i[a] != j[b]) b = next[b];
            if (i[a] == j[b]) ++b;
            if (b == lj) printf("%d\n",a - b + 2),b = next[b];
        }
        for (int a = 1; a <= lj; a++) printf("%d ",next[a]);
    return 0;
}

总 616ms~ 慢了点呢

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值