字符串匹配 & KMP算法

初识KMP

  • 期末的时候学习了KMP算法,虽然一开始的确听得是一头雾水,但是到现在,已经基本懂得了其中的原理,于是在这里把自己的理解写出来,再配上自己做的图示,希望对大家的学习有帮助,要是有什么疑问或建议,欢迎留言评论。^-^
  • KMP是一种用于字符串匹配的算法,至于为什么叫KMP呢,那是因为它是由D.E.Knuth和J.H.Morris和V.R.Pratt同时发现的,所以人们称它为K-M-P算法。
  • 所谓字符串匹配操作,可以这样理解:
    • 有一个长度为m 的字符串S,我们可以称它为目标串
    • 有一个长度为 n 的字符串T,我们可以称它为模式串
    • 字符串匹配操作所要做的,就是要你在S中找出所有与T相同的子串

方法讲解

下面就来讲一讲KMP其中的思路 (为程序方便,这里 S 和 T 两个字符串下标都是从0开始的) (因为对于不同的题目,对结果要求可能不同,所以程序中处理答案的部分就直接用“`ans++“`来代替)

暴力方法

讲KMP之前,我觉得应该先说一说最暴力的方法,也就是说直接逐位比较,先比较S[0..n-1]和T[0..n-1]是否一样,然后比较S[1..n]和T[0..n-1]是否一样,依此类推

void baoli(){
    for (int i=0; i+n<=m; i++){
        int j;
        for (j=0; j<n && S[i+j]==T[j]; j++);
        if (j>=n) ans++;                    //j>n代表整个T串都匹配成功了
    }
}

《字符串匹配 & KMP算法》

思考

数据小还没什么,可一旦数据变大,这种暴力方法就变得很慢了,可以看出,这种暴力在最倒霉的状况下(比如 S 和 T 都是一长串相同的字母),时间几乎可以到达O(n*m),这时,就会想到优化一下这个方法,于是人们就发明出了KMP。

KMP方法

  • 从前面的暴力方法中可以发现,时间大多是浪费在很多重复的不合法位置上,也就是说,比如在S中匹配到T的第i位发现对不上(失配)了,若用暴力方法,则只是单纯地往后移一位,要是可以预处理出这时应给往后移动几位,那么速度就会快很多。

next数组

  • 那么,我们就可以先对T预处理出一个next数组,表示满足T[0..x+1]=T[(i-x)..i]的最大的x
  • 可以想象图中黑长条是T,而两段小蓝条是相等的,且对于当前的i没有更长的这样的蓝条,那么图中蓝色箭头指向的就是next[i]了
    《字符串匹配 & KMP算法》
  • 要怎么求这个next数组呢?可以这样:
    • 初始一下next[0]=-1
    • 枚举 i 从1到n,依次求next[i]
    • 再用一个变量 k 记录答案,初始值为next[i-1],即图中左边那个篮框的位置,不过此时 k+1 这里不确定是不是i那个字符,再判断一下,若T[k+1]与T[i]不同,则k=next[k],直到k小于零为止。
void get_next(){
    next[0]=-1;
    for (int i=1; i<n; i++){
        int k=next[i-1];
        while (k>-1 && T[k+1]!=T[i]) k=next[k];
        if (T[k+1]==T[i]) k++;
        next[i]=k;
    }
}
  • 附上几组求next数组的演示动画,可以截图下来慢慢看
  • ababa
  • 《字符串匹配 & KMP算法》
  • ababacabc
  • 《字符串匹配 & KMP算法》

kmp

接下来就简单了,看一看动画,再看一看程序应该就可以差不多懂了,之前手画了一个示意图,T数组当时用的是P,看看画得清不清楚~
《字符串匹配 & KMP算法》

void kmp(){
    int k=0;                //k代表T字符串中T中k以及之前的都匹配好了(T[0..k]=S[i-k-1..i-1])
    for (int i=0; i<m; i++){        //i代表S字符串当前匹配位是第i位
        while (k>-1 && S[i]!=T[k+1]) k=next[k];     //可以看做T一直向右移知道匹配成功或不能移了
        if (S[i]==T[k+1]) k++;  //若T[k+1]位与S[i],说明第k+1位也是匹配成功的,k就再加一下
        if (k==n-1) k=next[k], ans++;   //要是k是T的最后一位,说明T整串都匹配成功,处理一下答案
    }
}

第三行代表着下一个T应跳的位置
S: ababcabcababcababcac
T: abcababc
《字符串匹配 & KMP算法》

S: ababababcababdababadb
T: ababa
《字符串匹配 & KMP算法》

结束

可以看到,用了kmp之后,时间复杂度差不多就是O(m)了很快吧!
学完之后要是还不太懂,可以多打几遍,做些题,打多了就熟了,下面附上洛谷3375-kmp模板题的程序(这题题面上n和m是和程序里是反过来的,不要介意)

#include <cstdio>
#include <cstring>
using namespace std;
char S[1000010],T[1000010];
int m,n,next[1000010];

void get_next(){
    next[0]=-1;
    for (int i=1; i<n; i++){
        int k=next[i-1];
        while (k>-1 && T[k+1]!=T[i]) k=next[k];
        if (T[k+1]==T[i]) k++;
        next[i]=k;
    }
}

void kmp(){
    int k=0;
    for (int i=0; i<m; i++){
        while (k>-1 && S[i]!=T[k+1]) k=next[k];
        if (S[i]==T[k+1]) k++;
        if (k==n-1) k=next[k],printf("%d\n",i-n+2);
    }
}

int main(){
    scanf("%s%s",S,T); m=strlen(S); n=strlen(T);
    get_next();
    kmp();
    for (int i=0; i<n; i++) printf("%d ",next[i]+1);
}
    原文作者:KMP算法
    原文地址: https://blog.csdn.net/jackypigpig/article/details/54809839
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞