zoukankan      html  css  js  c++  java
  • KMP算法详解

    本文的是基于我对邓俊辉老师编著《数据结构(C++语言版)(第3版)》上关于KMP算法的理解,和网络上一些大神们写的博客,所写。建议将我写的关于implement strstr这题的博客和本篇连起来读。

    不难发现,这里存在大量的局部匹配,针对暴力解法,若每次匹配的过程都是最后一位失配(即不匹配),文本串和模式串都得回退,并从头开始下一轮的尝试,这样浪费很多时间。

    一、主算法

    1)我们可以利用以往的成功对比所提供的信息,不仅可避免文本串字符指针的回退,而且可使模式串尽可能大跨度的地右移。我们举一个例子来得出KMP的思考过程;

    本轮发现:T[i]=‘E’ !=‘O’=P[4]失配以后,在保持i不变的同时,应将模式串P右移几个单位?,不难看出移动一个或两个单元是徒劳的,观察得出T[i-4,i) = P[0,4) = 'REGR'。也就是说,若能通过将P右移来实现在此局部匹配,如:比较到了T[i]说明,不管T[i]是否匹配,至少T[i]左侧的若干字符均应匹配,P[0]和T[i-1]对齐时,就是这种情况。若注意到 i-1是能够如此匹配的最左侧,即可直接将P右移4-1=3个单位(等效于i保持不安,同时令 j=1),然后继续对比。

    2)如图,如上例中,对比终止于T[i] !=P[j],按以上构想,指针 i 不必回退,而将T[i]与P[t] 对齐开始下一轮对比,那么关键是:t准确应取做多少?

    在上一轮的对比中可知,P[0,j) =T[i-j,i) ,若模式串P经过适当的右移以后,能够与T的某一(包含T[i]在内的)子串完全匹配,则必定满足的一项必要条件是:P[0,t)=T[i-t,j)=p[j-t,j),亦即,在P[0,j)中长度为t的真前缀应与长度为t的真后缀完全匹配,故t必来自集合:

    N(P,j) ={ 0<=t<j | P[0, t) = P[j-t, j) }                      .......(1)

    一般,该集合中,可能包含多个这样的t ,但值得注意的是:t 值的构成,仅取决于模式串P以及前一轮对比的首个失配位置P[j] ,而与文本串T无关。若下一轮从 T[i] 与 P[t] 对比开始,等效于将P右移了 j-t 个单元,与 t 成反比。为保证指针 i 绝不退后,同时又不遗失可能的匹配,应保证,选取最大的 t 值,保证,移动的距离最短。这里很关键的是如何选取 t 值,即next[ ]的构造。

    二、next表

    1)只要 j >0,即失配时,模式串P的下标大于0,则必有0属于集合N(P ,j),也就是说,针对表达式(1)中的集合非空,最坏的情况是 t=0,即模式串又重新的从开始和文本串T[i]开始对比,相当于向右移动 j 个单位。下表的意思是,当失配的位置rank的值等于0~9时,模式串下轮对比从P的那个位置开始。如:rank=3时失配,因为,只有当 t =0 时,才满足表达式(1),即P[0,3)中真前缀没有和真后缀匹配的情况,则下轮比较还是要从模式串的首元素开始比较,所以next[3] =0,即向右移动3个单位;当rank=6时,P[0,2) =P[4,6) ,所以下一轮的对比中,应该是 P[2]和T[i],做对比,而对模式串P而言相当于向右移了6-2=4个单位。如果某一轮的对比中,首字符就失配,此时应该进行时,用模式串的首字符和文本串T的下一字符做比较,为统一程序和达到依旧用首字符和文本串中的下一位做比较的目的,可以令next[0]=-1,这样,i 和 j都++以后,依旧是符合要求的。故在主程序中,i++和 j++的条件是 if(0>j || T[i]==P[j]) 。

     2)经过1)我们得到了next[j] ,但是如何计算出next[j+1]了?

    若next[j] = t ,则意味着在P[0,j)中,自匹配的真前缀和真后缀的最大长度为t ,故必有 next[j+1] <=next[j]+1 ,当且仅当P[j] = P[t]时,取等号。等号怎么理解了?如图:

    针对模式串P中的 j 点,满足P[0,t)=P[j-t,j),则当P[t] =P[j] 时,对模式串P的 j+1 点而言,应该是P[0,t+1) =P[j-t, j+1) ,而P[0,t+1)等于next[j]+1。至于为什么是最大值,这点比较好想,这里就不解释了。

    那么一般的情况,若P[j] !=P[t]时,又该如何的得到next[j+1] ?这点可以想到,若是从next[j+1]开始比较,说明,P中j点而言都是满足P[0,t ) =P[j-t),同时也肯定存在P[t]=P[j]。由next表的功能定义可知,next[j+1]从next[next[j]+1 ]+1,next[ next [next[j] ] ]+1..........中按照优先次序遍历寻找P[j] =P[t] ,即,反复用next[t] 替代 t(令 t=next[t] ) ,从而可令next[j+1] = next[t]+1 。具体的程序过程参考文章开始说的博客。

    3)若在对比的过程中出现这种情况,文本串T为0001000010,而模式串P为000010,又会出现什么情,下面先给出模式串P的next表。

    在上一轮的对比中,因T[i] ='1' !='0'=P[3]而失配,接下来按KMP算法将依次将P[2]、P[1]、P[0]和T[i]做对比,但这三次的对比都会失败,那这些对比可以避免吗?注意到P[3] = P[2] =P[1] =P[0] ='0',就可以得出,若是T[i] !=P[3],则继续将T[i] 和那些与P[3] 相同的字符作对比,都是徒劳的。所以我们可以将公式(1)修改为:

    N(P,j) ={ 0<=t<j | P[0, t) = P[j-t, j),且P[t] !=P[j}  }    

    改进后的算法,区别在于,每次在P[0, j )中发现长度为t 的真前缀和真后缀匹配后,还需进一步检查P[j ]是否等于 P[t]。唯有不等时,才能将t 赋予next[j],否则,需转而代之以 next[t ]。

    三、代码如下

     1 class Solution {
     2 public:
     3     int strStr(char *T, char *P) 
     4     {
     5         int n=strlen(T),i=0;
     6         int m=strlen(P),j=0;
     7 
     8         vector<int> next(m);    //构建next表
     9         getNext(P,m,next);
    10 
    11         while(j<m&&i<n)
    12         {
    13             if((0>j||T[i]==P[j]))   //两者顺序不可交换,意味:表头比较或者匹配
    14             {
    15                 i++;
    16                 j++;
    17             }
    18             else
    19                 j=next[j];
    20         }
    21         return i-j;
    22     }
    23 
    24     vector<int> getNext(char *P,int m,vector<int> &next)
    25     {
    26         int t=-1;
    27         next[0]=-1;     //表头不匹配的情况
    28         int j=0;
    29         while(j<m-1)
    30         {
    31             if(0>t||P[j]==P[t]) //next表在P[j]==P[t]的基础上才能看j+1
    32             {
    33                 j++;
    34                 t++;
    35                 next[j]=(P[j] !=P[t]?t:next[t]);    //改进点
    36             }
    37             else
    38                 t=next[t];
    39         }
    40         return next;
    41     }
    42 };
  • 相关阅读:
    java-判断某一年是否是闰年
    java-不用其他变量实现两变量互换
    java基础一标识符,运算符
    robotium学习(三)
    robotium学习(二)
    robotium学习(一)
    AX2012 订单折扣的应用
    AX2012 用户收藏夹复制
    ueditor 改变图片上传路径
    if else 多路分支,多条件运算
  • 原文地址:https://www.cnblogs.com/love-yh/p/7062068.html
Copyright © 2011-2022 走看看