【算法】KMP算法

简介

KMP算法由 Knuth-Morris-Pratt 三位科学家提出,可用于在一个 文本串 中寻找某 模式串 存在的位置。
本算法能够有效下降在一个 文本串 中寻找某 模式串 过程的时间复杂度。(若是采起朴素的想法则复杂度是 \(O(MN)\)c++

这里朴素的想法指的是枚举 文本串 的起点,而后让 模式串 从第一位开始一个个地检查是否配对,若是不配对则继续枚举起点。算法

前置知识

真前缀
指字符串左部的任意子串(不包含自身),如 abcde 中的 a,ab,abc,abcd 都是真前缀但 abcde 不是。数组

真后缀
指字符串右部的任意子串(不包含自身),如 abcde 中的 e,de,cde,bcde 都是真后缀但 abcde 不是。函数

前缀函数
一个字符串中最长的、相等的真前缀与真后缀的长度, 如AABBAAA对应的前缀函数值是 \(2\)ui

原理

注意:在分析的时候,咱们规定字符串的下标从 \(1\) 开始。spa

开始:
咱们记扫描模式串的指针为j,而扫描文本串的指针为i,假设一开始i,j都在起点,而后让它们一直下去直到彻底匹配或者失配,好比:指针

j
ABCD

i
ABCDEFG

而后code

j
ABCD

 i
ABCDEFG

最后在此完成了一次匹配,相似地若是ABCD改成ABCC则在此失配。对象

j
ABCD

   i
ABCDEFG

i,j运做模式如上。ci



KMP算法就是,当模式串和文本串失配的时候,j指针从真后缀的末尾跳到真前缀的末尾,而后从真前缀后一位开始继续匹配。(从而起到减小配对次数,这即是KMP算法的核心原理)

结合例子解释:

模式串: \(AABBAAA\)

文本串: \(AABBAABBAAA\)

j指针在最后一个A处失配。

j
AABBAAA
      i
AABBAABBAAA

由于此时 以j为尾的前缀 所对应的前缀函数值是 \(2\) ,因此 j指针 跳到这里:

j
AABBAAA
      i
AABBAABBAAA

而后从下一位开始继续配对:

j
AABBAAA
      i
AABBAABBAAA

最后

j
AABBAAA
          i
AABBAABBAAA

能够看出,KMP可以有效减小配对次数。

实现

咱们记模式串p文本串s

从上面的模拟中,咱们发现须要预处理出一个数组(记之为next[]),它储存模式串中前缀对应的前缀函数\(\pi()\),如对于字符串ABCABC

\(\pi(0)=0\) (由于什么都没有)
\(\pi(1)=0\)A甚至没有真前缀真后缀
\(\pi(2)=0\)AB
\(\pi(3)=0\)ABC
\(\pi(4)=1\)ABCA
\(\pi(5)=2\)ABCAB
\(\pi(6)=3\)ABCABC

一样地,咱们发现若是用暴力朴素的想法来统计复杂度是 O(N^2) 很差,因而采用相似于上面的方法,只不过模式串配对的对象是本身罢了。

能够结合代码理解,并注意举例,尝试在纸上模拟这个过程。

for(int i=2,j=0;i<=lenp;i++){
        while(j && p[j+1]!=p[i]) j=next_[j]; // 若是j指向元素的下一个元素会和当前配对位置失配,则j跳回去
        if(p[j+1]==p[i]) j++; //若是可以配对上,j++
        next_[i]=j; //记录当前位置的前缀函数π
}

完整代码:

#include<bits/stdc++.h>
using namespace std;

const int N=1e6+5;
char p[N],s[N];
int next_[N];

int main(){
    cin>>s+1>>p+1;

    int lenp=strlen(p+1),lens=strlen(s+1);
    // build next array
    for(int i=2,j=0;i<=lenp;i++){
        while(j && p[j+1]!=p[i]) j=next_[j]; // 若是j指向元素的下一个元素会和当前配对位置失配,则j跳回去
        if(p[j+1]==p[i]) j++; //若是可以配对上,j++
        next_[i]=j; //记录当前位置的前缀函数π
    }

    for(int i=1,j=0;i<=lens;i++){
        while(j && p[j+1]!=s[i]) j=next_[j];
        if(p[j+1]==s[i]) j++;

        // if match
        if(j==lenp){
            j=next_[j];
            cout<<i-lenp+1<<endl;
        }
    }

    for(int i=1;i<=lenp;i++) cout<<next_[i]<<' ';
    cout<<endl;

    return 0;
}

复杂度

\(O(N+M)\)

相关文章
相关标签/搜索