KMP

字符串匹配的朴素算法,我就不讲了,我们直接进入KMP算法

KMP算法的优化在哪里

正常的朴素算法n^2是枚举每一个字符串每一个点为起点,与模板进行比对,比对一次是O(m)的复杂度
所以总复杂度就是O(nm),这样的复杂度显然不符合要求,但我们可以发现对于这样的例子:



我们在发现了第四位B和D不一样了以后我们不是单纯的将起点右移一位,此时 i=4,j=3,
将a[i]与a[j+1]比较发现不同,我们将j返回到模板中nxt[j] (nxt数组将在后文讲到),j于是变成1


我们再次比较a[i]与a[j+1]发现相同,再i++,继续判断,以此类推

那么nxt[j]是什么以及我们应该如何预处理出模板中nxt[j]
1. 首先,对于一个字符串,我们要了解两个概念:”前缀”和”后缀”。

“前缀”指除了最后一个字符以外,一个字符串的全部头部组合;”后缀”指除了第一个字符以外,一个字符串的全部尾部组合。
对于字符串:"bread"
前缀: b , br , bre, brea
后缀: read, ead, ad , d

2. 那么知道了什么是前缀后缀,我们再来求nxt数组,通俗解释是”前缀”和”后缀”的最长的共有元素的长度就是nxt中存的值
拿"ABCDABD"举例*

nxt[1]=0    "A"的前缀和后缀都为空集,共有元素的长度为0;

nxt[2]=0    "AB"的前缀为[A],后缀为[B],共有元素的长度为0;

nxt[3]=0    "ABC"的前缀为[A, AB],后缀为[BC, C],共有元素的长度0;

nxt[4]=0    "ABCD"的前缀为[A, AB, ABC],
后缀为[BCD, CD, D],共有元素的长度为0;

nxt[5]=1    "ABCDA"的前缀为[A, AB, ABC, ABCD],
后缀为[BCDA, CDA, DA, A],共有元素为"A",长度为1;

nxt[6]=2    "ABCDAB"的前缀为[A, AB, ABC, ABCD, ABCDA],
后缀为[BCDAB, CDAB, DAB, AB, B],共有元素为"AB",长度为2;

nxt[7]=0    "ABCDABD"的前缀为[A, AB, ABC, ABCD, ABCDA, ABCDAB],
后缀为[BCDABD, CDABD, DABD, ABD, BD, D],共有元素的长度为0。

3.那么我们应该如何o(m)的求出模板的nxt预处理呢
其实采用的思路和查询操作相似
初始状态如下


当j==0零的时候我们要进行特判,因为nxt[0]==0,它将永远出不了循环所以当a[j+1]==a[i]或者j==0时就不继续j=nxt[j]而是跳出循环
实现如下

inline void getn(char a[N])
{
    for(register int i=2,j=0;i<=m;i++)
    {
        while(j&&a[j+1]!=a[i]) j=nxt[j];
        if(a[j+1]==a[i]) j++;
            nxt[i]=j;
    }
}

而完成了这步操作以后搜索操作也和这个差不多

实现如下:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <map>
#include <cstdlib>
#include <algorithm>
#include <queue>
#include <stack>
#include <vector>
#include <ctime>
using namespace std;
const int N=1000100;
char s1[N],s2[N];
int n,m,nxt[N],cnt=0;
inline void getn(char a[N])
{
    for(register int i=2,j=0;i<=m;i++)
    {
        while(j&&a[j+1]!=a[i]) j=nxt[j];
        if(a[j+1]==a[i]) j++;
        nxt[i]=j;
    }
}
inline void search(char a[N],char b[N])
{
    for(register int i=1,j=0;i<=n;i++)
    {
        while(j&&b[j+1]!=a[i]) j=nxt[j];
        if(b[j+1]==a[i]) j++;
        if(j==m) printf("%d
",i-m+1);
    }
}
int main()
{
    scanf("%s%s",s1+1,s2+1);
    n=strlen(s1+1),m=strlen(s2+1);
    getn(s2);search(s1,s2);
    for(register int i=1;i<=m;i++)
        printf("%d ",nxt[i]);
    return 0;
}
原文地址:https://www.cnblogs.com/cold-cold/p/10052783.html