KMP算法详解

前置:


 

  尝试去思考这样一个问题:给定字符串S和P,询问在字符串S中,字符串P出现了几次?

   设S = "aabcdedf", P = "abc"; 

   我们先从最暴力的方法入手,不难想到去针对S的每一位去和P暴力匹配。

   当 $i = 0$ 时, 字符串匹配如下图:

   

   $s[0] == p[0]$ 则继续向后匹配,发现 $s[1] != p[1] $,则执行 $i++$ 进行下一次匹配。

   当 $i = 1$ 时,匹配如下图

 

    

   $s[0] == p[0]$ 继续向后匹配, 直到 $s[i + strlen(p) - 1] == p[strlen(p) - 1]$ 证明匹配成功。

   令 $ans++$,并且 $i++$ 进行下一次匹配。

   按上述方法匹配直到字符串S被遍历一遍,输出 $ans$ 即可。

   代码:

 1 int fun(char *s, char *p){
 2     int ans = 0;
 3     for (int i = 0; i < strlen(s); ++i){
 4         int j = 0; 
 5         for (; j < strlen(p); ++j){
 6             if (s[i + j] == p[j])
 7                 continue;
 8             else
 9                 break;
10         }
11         if (j == strlen(p))
12             ++ans;
13     }
14     return ans;
15 }
View Code

   设字符串S的长度为 $n$ , P的长度为 $m$ ,不难计算出该暴力算法的复杂度为 $O(nm)$,在数据量较大的时候必定超时。

KMP算法:


  KMP算法则是对上述过程中的一个优化,使得在每次匹配的过程中,当遇到失配的情况时,可以通过之前已经匹配的信息优化匹配过程,而不用每次都从字符串P的起始位置重新匹配。

  这个优化的过程就是通过 $next$ 数组实现的。 我们将字符串S称为文本串,将字符串P成为模式串

  KMP算法分为两个阶段:1、求模式串的 $next$ 数组。 2、结合 $next$ 数组进行匹配。

  $next$ 数组:$next[i]$ 的含义为:模式串第 $i$ 位与文本串适配时,下一次匹配应该从 模式串第 $next[i]$ 个位置开始匹配。

  图例:

  

  上图匹配过程中在 $p = 6$ 时发生失配,结合 $next$ 数组此时我们下一次匹配的起始位置应是:

  

  因为右移四位后,模式串中又会有一个“AB”与文本串匹配。从而不用 $i$ 移动。

  不难得出结论: $next[i]$ 为 $i$ 之前的模式串的最长公共前后缀的长度

  之后去求 $next$ 数组的过程其实是模拟串自我匹配的过程

  

int nxt[1005];

void get_next(char *s){
    int lens = strlen(s);
    int i = 0, j = -1;
    nxt[0] = -1;//默认nxt[0] = -1
    while (i < lens){
        //如果j = -1 或者当前两位相同,则最长公共前后缀可往后扩展。 
        if (j == -1 || s[i] == s[j]){
            ++i;
            ++j;
            nxt[i] = j;//记录当前位置最长公共前后缀的长度。 
        }
        //失配则i不变, j需移动到nxt[j]的位置继续匹配 
        else{
            j = nxt[j];
            
        } 
    } 
}
View Code

 

  在求得 $next$ 数组之后,我们便可以根据 $next$ 数组进行匹配。

int kmp(char *s, char *p){
    //s为文本串, p为模式串
    int lens = strlen(s), lenp = strlen(p);
    int ans = 0;
    int i = 0, j = 0;
    while (i < lens){
        //若j = -1或者匹配成功则继续匹配 
        if (j == -1 || s[i] == p[j]){
            ++i;
            ++j;
        }
        //失配则i不变, j移动到nxt[j]位置继续匹配 
        else{
            j = nxt[j];
        } 
        //匹配成功 
        if (j == lenp)
            ++ans; 
    } 
    return ans; 
} 
View Code

  以上便是KMP算法的大体内容,复杂度为 $O(n + m)$。

 

  

  

posted @ 2019-11-06 18:17  ZSsst  阅读(230)  评论(0编辑  收藏  举报