KMP匹配字符串


一、前言

这一章记录的是自己学习KMP算法的一个笔记吧。我觉得KMP虽然目前没有用到相关的算法,但是他的思想很妙,很多人在刚开始会非常不理解这个算法的由来。而我也是写了好几遍才能够真的说掌握了一些KMP算法。其实主要记住一点就是,KMP完成了字符串与本身进行比较的一个思路。

所有字符串下标从1开始

二、相关操作

①相关变量的定义

说真的我觉得相关一些数组的定义是整个这个算法的核心

首先就是题目给的两个串,一个比较长的叫做模式串,另外一个叫做子串,题目的要求就是子串在模式串出现的位置或者出现的次数

我们再看最难理解的一个ne数组的定义,最长子串的公共前后缀长度

其实这个地方需要加上一个定义,假设ne[X], 这个其实代表的是,子串从1-X位置的字符串的最长公共前后缀的长度。这里要说明的是,前后缀是什么

  • 前缀:包含字符串首字母的连续子串
  • 后缀:包含字符串尾字母的连续子串

比如字符串:abc

前缀有: a ab

后缀有: c bc

②ne数组的求解

搞清楚了前后缀之后,如果我们要去求ne数组怎么求呢?

假设我们有一个字符串:abcabf

a : 0

ab : 0

abc : 0

abca : a为公共前后缀 1

abcab: ab为公共前后缀 2

abcabf: 0

而上方求出这样一个数组便是去比较p字符串的本身,匹配图如下:

代码如下:

for(int i = 2, j = 0; i <= n; i ++ ) {
        while(j != 0 && p[j + 1] != p[i]) j = ne[j];
        if(p[j + 1] == p[i]) j ++ ;
        ne[i] = j;
    }

③字符串匹配

为什么求出一个ne数组就可以拿来进行匹配了呢?

这个是我最常用进行的一个理解图

我们可以看到有两个子串

  • q : abcabcabf
  • p : abcabf

当p匹配到f的时候发现不相等了,那么子串p重新开始匹配的位置是由ne数组进行指示的,f位置前一个位置是b,b的ne值为2,代表此位置的公共前后缀的长度为2,那么我们需要先回到2的位置,也就是p串中的b位置,再看b后面的c是否与上面串相等。相等之后继续往后推移匹配p的串的指针j,知道j指针的值等于p的长度,说明已经匹配成功了。

上面之所以能进行,其实就是因为,p利用ne指针去指示已经匹配过的前缀,我们不再进行重新匹配,而是从匹配好的前缀再重新开始,也就是说,abf和q串的abc字串不相等了,但是abc已经完全匹配好了ab后缀,那我的p串刚好有个ab前缀,那我们直接从ab这个前缀的位置再继续往后匹配,这样就避免了重复匹配的时间复杂度!

三、相关题目

完整AC代码

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 100050, M = 10 * N;
char p[N], q[M];
int ne[N];

int main() {
    //进行相关输入
    int n, m;
    cin >> n >> (p + 1) >> m >> (q + 1);
    
    //先进性ne数组的求解
    for(int i = 2, j = 0; i <= n; i ++ ) {
        while(j != 0 && p[j + 1] != p[i]) j = ne[j];
        if(p[j + 1] == p[i]) j ++ ;
        ne[i] = j;
    }
    
    //进行字符串的匹配
    for(int i = 1, j = 0; i <= m; i ++ ) {
        while(j != 0 && p[j + 1] != q[i]) j = ne[j];
        if(p[j + 1] == q[i]) j ++ ;
        if(j == n) {
            cout << i - n << " ";
            j = ne[j];
        }
    }
    
    return 0;
}

文章作者: sheepice
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 sheepice !
  目录