KMP的模板及理解

kmp算法是有三个人联合起来缔造的一个算法,具体是谁不重要.

这种算法用于计算一个a串内b串的出现次数和位置.

这个算法的核心就是一个失配函数.

具体先看暴力算法.

暴力算法一般是这么写的.

首先维护两个指针i和j,这两个指针分别是a串搜到的位置和b串搜到的位置.

然后每次都判断一下若a[i]等于b[j],则i与j都加1.

否则就说明从这个位置无法匹配,还原.

最坏时间复杂度为O(nm).

不过一般情况下时间复杂度还是不错的.

代码如下:

inline void work(){
  //这里的字符串是以a[0]和b[0]开头的,b是模板串,仅求次数sum 
  int j=0;
  for (int i=0;i<n;i++){
    j=0;
    while (a[i+j-1]==b[j]&&j<m-1) j++;
    if (j==m-1) sum++;
  }
}

朴素算法很简单.

不过kmp算法更快,时间复杂度为O(n+m).

几乎是理论下界了.

我们可以这么理解kmp算法.

就像对这两个串匹配:


我们匹配先是匹配第一个位置:


在第6个位置我们会失配,这时朴素算法只是向后移一位:


依旧失配.

但kmp认为这肯定行不通,应该这样判断移几位:

判断这个位置应该移几位不应该看成是把B串向后移,而应该看成是将这个串的匹配位置放到哪里.

就是说将这个串偏移几位,主要看这个串的位置已经匹配过的有多少.

也就是说应该以哪个点作为第一个匹配的点.

就像这里应该这样偏移:


这就解释了kmp的偏移准则:重复匹配的越多越好.

kmp就认为ABABAC与ABABAB比较的过程中,最后一个字符失配了.

那么我们要移得越少越好,但是一定要是上一次失配的位置之前的字符全都匹配的情况下.

也就是说这里ABA是可以重叠的.

那么这个东西怎么算我们就需要用到字符串本身的特性了.

我们发现,我们匹配到B串是第i位的时候(字符串均以第0位开始)偏移最少,我们就得让与第i-j+1~i位相等的第0~j-1位更多.

于是我们用next[i]表示第i为的失配后最多的重叠个数.

然后我们就可设计出一个算法框架:

1.枚举一个i表示当前扫到B串的位置.

2.每一次枚举都看看B[j+1]是否等于B[i],若不是说明我们需要往前,于是j=p[j].

3.判断是否最后找到了一个可以匹配的位置,若找到了,j++.

4.p[i]=j.

很多人说这就是个自我匹配,我看挺像.

于是自我匹配部分代码如下:

inline void mateme(){
  next[0]=-1;      //沉痛的教训,一定要把next[0]定为-1,然后吧i从1开始枚举,不然查也查不出错来
  int j=-1;
  for (int i=1;i<m;i++){
  	while ((j>-1)&&(b[j+1]!=b[i])) j=next[j];
  	if (b[j+1]==b[i]) j++;
  	next[i]=j;
  }
}

然后匹配过程就很清晰了:

inline void mate(){
  int j=-1;
  for (int i=0;i<n;i++){
  	while ((j>-1)&&(a[i]!=b[j+1])) j=next[j];
  	if (b[j+1]==a[i]) j++;
  	if (j==m-1) {
  	  sum++;j=next[j];
  	}
  }
}

这是整个kmp算法的框架.

接下来是复杂度证明.

我们先看B匹配A的部分.

由于i肯定是枚举n次没问题.

难点在于j上.

j是不能小于0的,且让j减小的次数是不确定的.

让j增大的语句最多只能执行n次,且每次只增加1.

那么减小的语句肯定也只有最多n次执行.

所以是线性的.

同理,自我匹配部分也可以这么证明.

猜你喜欢

转载自blog.csdn.net/hzk_cpp/article/details/79589074