• KMP算法介绍


    简介

    KMP算法是D.E.Knuth、J.H.Morris和V.R.Pratt共同提出的,称之为Knuth-Morris-Pratt算法,简称KMP算法。该算法与Brute-Force算法相比有较大改进,主要是消除了主串指针的回溯,从而使算法效率有了某种程度的提高。

    实现

    1、从模式串t中提取加速匹配的信息

    kmp就是通过模式串本身的特点来加速的,具体来说就是求next数组,next数组的定义如下:

    $$next[j]=egin{cases} -1 & 当j=0时\MAX{k \,|\, 0<k<j { } and { } "t_0t_1cdot cdot cdot t_{k-1}"="t_{j-k}t_{j-k+1}cdot cdot cdot t_{j-1}"}&前后缀相等时\0 &其它情况end{cases}$$

    next数组的求解过程如下:

    1. next[0]=-1(特殊值,标记),next[1]=0(j=1,在1~j-1的位置上没有字符,属于其它情况)
    2. 如果next[j] = k,表示有$"t_0t_1cdot cdot cdot t_{k-1}"="t_{j-k}t_{j-k+1}cdot cdot cdot t_{j-1}"$:
    • 若$t_k=t_j$,即有$"t_0t_1cdot cdot cdot t_{k-1}t_k"="t_{j-k}t_{j-k+1}cdot cdot cdot t_{j-1} t_j"$,显然有$next[j+1]=k+1$。
    • 若$t_k eq t_j$,说明$t_j$之前不存在长度为$next[j]+1$的字串和开头字符起的字串相同,那么是否存在一个长度较短的字串和开头字符起的字串相同呢?设${k}'=next[k]$(回退),则下一步应该将$t_j$与$t_{{k}'}$比较:若$t_j=t_{{k}'}$,则说明$t_j$之前存在长度为$next[{k}']+1$的字串和开头字符起的字串相同;否则依此类推找更短的字串。知道不存在可匹配的字串,置$next[j+1]=0$。所以,当$t_k eq t_j时,置$k=next[k]$ 

    例如:

    求模式串$t="aaaba"$的next数组。

    解:

    j 0 1 2 3 4
    t[j] a a a b a
    next[j] -1 0 1 2 0
     1 //char x[]是模式串
     2 void pre_kmp()
     3 {
     4     int m = strlen(x);
     5     int i = 0, j = nexts[0] = -1;
     6     while (i < m)
     7     {
     8         while (j != -1 && x[i] != x[j])  j = nexts[j];    //当前不匹配,j回退,寻找是否存在一个长度较小的字串和开头的字串相等
     9         nexts[++i] = ++j;                //j等于已匹配的长度,如果当前位置也匹配,则nexts直接为j+1
    10     }
    11 }

    2、KMP算法的模式匹配过程

    简单的说就是,若当前位置匹配则模式串和主串指针同时后移一位,若当前位置不匹配,则主串指针不动,模式串指针回退到next[i],如果回退的位置上仍不匹配继续回退。

    大概这样:

     1 //返回x在y中出现的次数(可重叠)
     2 int KMP_Count()    //x为模式串,y为主串
     3 {
     4     int i = 0, j = 0, ans = 0;
     5     int m = strlen(x), n = strlen(y);
     6     pre_kmp();
     7     while (i < n)
     8     {
     9         while (j != -1 && y[i] != x[j])  j = nexts[j];        //当前位置不同,j回退
    10         i++; j++;                //当前位置相同,i、j同时后移一位
    11         if (j >= m)
    12         {
    13             ans++;
    14             j = nexts[j];
    15         }
    16     }
    17     return ans;
    18 }

    注:

    • 匹配时分为主串可重复和主串不可重复,两者只是在找到匹配串时模式串的回溯位置不同
    • next数组保证了真前缀和真后缀尽可能长的匹配,这样才能保证匹配时不会出现遗漏,同时模式串也能右移的更多
    • pre_kmp求next数组时求了字符串最后一个字符的下一位,因为做题时经常需要这个值

    复杂度

    一般化结论: 
    - 一个周期内的比较次数:1 * (M - 1) + M 
    - 周期长度:M 
    - 周期个数:N/M 
    - 比较总次数: 周期个数 * 一个周期内额比较次数 = (2 - 1/M)*N < 2N

    所以最坏情况下模式串中每个字符的平均比较次数小于2,所以比较部分的平均时间复杂度为O(N)。

    求next数组的过程其实主串与主串比较(KMP是将主串与模式串匹配),所以时间复杂度为O(M)。

    总的时间复杂度为O(M+N)。

    参考链接:

  • 相关阅读:
    Kettle进行数据迁移(ETL)
    Visual Studio常用快捷键
    RESTful API 设计指南
    理解RESTful架构
    LINQ for XML简单示例
    .NET导入导出Excel方法总结
    AJAX + WebService 实现文件上传
    GitHub使用教程
    .NET读取Excel数据,提示错误:未在本地计算机上注册“Microsoft.ACE.OLEDB.12.0”提供程序
    ASP.NET 操作Excel中的DCOM配置方式
  • 原文地址:https://www.cnblogs.com/lfri/p/10339410.html
Copyright © 2020-2023  润新知