题目传送门

一、什么是KMP算法

1、代码随想录的理论篇
https://www.bilibili.com/video/BV1PD4y1o7nd

2、代码随想录的代码篇
https://www.bilibili.com/video/BV1M5411j7Xx

3、 第37讲,KMP算法
https://haokan.baidu.com/v?pd=wisenatural&vid=16960471433379543511

4、 第38讲,KMP2
https://haokan.baidu.com/v?pd=wisenatural&vid=12023963535622364019

二、实例计算NE数组

#include <bits/stdc++.h>

using namespace std;
const int N = 100010;
int n;
int ne[N];
char p[N];
// 测试用例:8 abababab
/**
人脑计算
ne[1]=0   ne[2]=0  ne[3]=1  ne[4]=2  ne[5]=3  ne[6]=4  ne[7]=5  ne[7]=6

代码实测
8 abababab
0 0 1 2 3 4 5 6

结论:一致
*/
int main() {
    cin >> n >> (p + 1);
    for (int i = 2, j = 0; i <= n; i++) {
        while (j && p[i] != p[j + 1]) j = ne[j];
        if (p[i] == p[j + 1]) j++;
        ne[i] = j;
    }
    for (int i = 1; i <= n; i++)printf("%d ", ne[i]);

    return 0;
}

三、C++ 代码

#include <bits/stdc++.h>

using namespace std;

const int N = 100010;   //模板串最大长度限制
const int M = 1000010;  //模式串最大长度限制

int n;          //模板串长度
int m;          //模式串长度
int ne[N];      //next数组
char s[M];      //模式串
char p[N];      //模板串
/**
 KMP的思想
 1、先考虑一下暴力怎么做?
 2、因为暴力作法,在对比多次后失败后,需要回到头才能跟目标串的下一个字符开始匹配,效率太差,需要优化。
 3、优化的思路受暴力作法的启发,试图在匹配过程中找到一些可以重复利用的特征,加快匹配的速度,即不想完全
从头再次对比了,能少退就少退。
 4、经画图可以发现,如果我们能整理出一套模板串前缀与后缀的公共子中的最大长度,发生不匹配的时候,不用
跳到头,按最大前后匹配子串长度跳最省时间。如果这时再发生对比不匹配问题,就再次尝试较小一点的前后缀相同字符,
依此类推,直到退无可退,那就从头开始对比。

 KMP匹配过程:对于模板串我们先提前预处里出ne数组,然后在匹配的时候,如果当前位置的模式串和模板串匹配,
那么指针向后移动,如果当前位置的模式串和模板串不匹配,我们就要将模板串的指针移动到 ne[j] 的位置继续匹配,
 当j移动到模板串最后的位置(等于模板串的长度),就说明匹配成功,模板串是模式串的字串。
 最长相等前后缀-(最长公共前后缀)
 */
int main() {
    cin >> n >> (p + 1) >> m >> (s + 1);
    //一、求ne数组
    //i:当前试图进行匹配的S串字符,j+1是模板串当前试图与S串i位置进行匹配的字符
    //j:退无可退,无需再退
    //i:是从2开始的,因为ne[1]=0,表示第1个不匹配,只能重头开始,不用算
    for (int i = 2, j = 0; i <= n; i++) {
        while (j && p[i] != p[j + 1]) j = ne[j];
        //如果是匹配情况发生了,那么j移动到下一个位置
        if (p[i] == p[j + 1]) j++;
        //记录j到ne数组中
        ne[i] = j;
    }

    //二、匹配字符串
    //i:当前试图进行对比的S串位置
    //j:最后一个已完成匹配的P串位置,那么,当前试图与S串当前位置i进行尝试对比匹配的位置是j+1
    for (int i = 1, j = 0; i <= m; i++) {
        while (j && s[i] != p[j + 1]) j = ne[j];//不行就退吧,当j==0时,表示退无可退,无需再退
        //如果是匹配情况发生了,那么j移动到下一个位置
        if (s[i] == p[j + 1]) j++;          //匹配则指针前行,i不用++,因为它在自己的for循环中,自带++
        if (j == n) {                       //如果匹配到最大长度,说明完成了所有位置匹配
            printf("%d ", i - n);     //输出开始匹配位置
            j = ne[j];                      //回退,尝试继续进行匹配,看看还有没有其它可以匹配的位置
        }
    }
    return 0;
}

四、无注释版本

#include <bits/stdc++.h>

using namespace std;
const int N = 100010, M = 1000010;
int n, m;
int ne[N];
char s[M], p[N];

int main() {
    cin >> n >> (p + 1) >> m >> (s + 1);
    for (int i = 2, j = 0; i <= n; i++) {
        while (j && p[i] != p[j + 1]) j = ne[j];
        if (p[i] == p[j + 1]) j++;
        ne[i] = j;
    }
    for (int i = 1, j = 0; i <= m; i++) {
        while (j && s[i] != p[j + 1]) j = ne[j];
        if (s[i] == p[j + 1]) j++;
        if (j == n) {
            printf("%d ", i - n);
            j = ne[j];
        }
    }
    return 0;
}

相关文章: