拓展KMP

 

•参考资料

  [1]:刘雅琼PPT讲义 [提取码:337l]

  [2]:kuangbin的模板

问题描述

  给定母串S,和子串T。
  定义 n = |S| , m = |T|,extend[ i ] = S[ i..n-1 ]与T[0...m-1] 的最长公共前缀长度。

  请在线性的时间复杂度内,求出所有的 extend[ 0..n-1 ]。

  注意:字符串下标从0开始

•解释

  

  n = |S| = 6 , m = |T| = 3;

  extend[0] = S[0….n-1]与T[0...m-1]的最长公共前缀,为aba,故extend[0] = 3。
  extend[1] = S[1….n-1]与T[0...m-1]的最长公共前缀,无,故extend[0] = 0。
  extend[2] = S[2….n-1]与T[0...m-1]的最长公共前缀,为aba,故extend[0] = 3。
  以此类推…………

•问题的解

  容易发现,如果有某个位置 i 满足 extend[ i ] = m;

  那么 T 就肯定在 S 中出现过,并且进一步知道出现首位置是 i,而这正是经典的KMP问题。
  例如:对上述样例分析,当 i = 0 时,extend[0] = 3,而 |T|=3,满足上述描述。
  因此可见“扩展的KMP问题”是对经典KMP问题的一个扩充和加难。

  

             (红色字母表示失配,在第10位置失配)

  这里为了要计算extend[0],需要进行11次比较运算,求出extend[0]=10。

  然后计算 extend[1] = 9;

  为了计算 extend[1],我们是不是也要进行10次比较运算呢?
  答案当然是否定的,因为通过计算 extend[0]=10,我们可以得到这样的信息:
    S[0..9] = T[0..9];
    S[1..9] = T[1..9];
  计算 extend[1] 的时候,实际上是 S[1] 开始匹配 T[0...m-1]。

  因为 S[1..9] = T[1..9],所以在匹配的开头阶段是 “以 T[1..9] 为母串,T为子串” 的匹配。

  设辅助函数 next[ i ] 表示 T[ i..m-1 ]与 T[0...m-1] 的最长公共前缀长度。
  对上述例子,next[1] = 10,也就是说:

    ①T[1..10]=T[0..9];
    ②T[1..9]=T[0..8];
  由 ①② 可推出 S[1..9] = T[1..9] = T[0..8] 。
  这就是说前9位的比较是完全可以避免的!

  我们直接从 S[10] ? T[9] 开始比较。

  这时候一比较就发现失配,因此extend[1]=9。

•拓展KMP算法

  下面提出一般的算法。
  定义P:在匹配 extend[ i ] 时,i 之前已匹配的最远的公共前缀,即 $P=\sum_{j=0}^{j<i}max\{extend_j\}$;

  设取最大值的 j 用字母 K 表示,即P = extend[ k ]。

  假设 extend[ 0…i ] 已经计算好,并且在之前的匹配过程中到达的最远位置为 P,取到最远位置 P 的下标为 K。
  

  根据定义:S[ K....P ] = T[ 0….P-K ] ⇔ S[ i+1...P ] = T[ i+1-K...P-K];
  设 L = next[ i+1-K ],则T[ i+1-K...L+(i+1-K)-1] = T[ 0...L-1 ];

  情况①:P-K ≥ L+(i+1-K)-1,即 P ≥ L+i,也就是说从 S[ i+1 ] 开始匹配,只匹配到了 T[ 0...L-1 ],如下图:

    

    上面红色部分代表相等部分,蓝色部分肯定不相等。
    因为 next[ i+1-K ] = L,如果蓝部分相等的话,next[ i+1-K ] 必定大于 L,而所求的next[]是正确的,与此矛盾;
    这个时候无需任何比较就得 extend[ i+1 ] = L,且不必更新 P,K; 
  情况②:P-K < L+(i+1-K)-1,也就是说从 S[ i+1 ] 开始匹配,目前匹配到了 T[ 0...P-i-1 ],如下图:

    

    上图蓝色部分是未知的,因为在计算extend[0...i]时,达到的最远位置为 P,所以P之后未被访问过。
    这种情况下,就要从S[ P+1 ] ? T[ P-i ] 开始匹配,直到失配为止,匹配完后,更新 P , K。

•Code

 1 char T[maxn];//母串,长度为 n
 2 char S[maxn];//子串,长度为 m
 3 int nex[maxn];//S[i...m-1]与S[0...m-1]的最长公共前缀
 4 int extend[maxn];//T[i,...,n-1]与S[0,...,m-1]的最长公共前缀
 5 void getNex
 6 {
 7     int len=strlen(S);
 8     nex[0]=len;
 9     int j=0;
10     while(j+1 < len && S[j+1] == S[j])
11         j++;
12     nex[1]=j;
13     int k=1;
14     for(int i=2;i < len;++i)
15     {
16         int P=k+nex[k]-1;
17         int L=nex[i-k];
18         if(L < P-i+1)
19             nex[i]=L;
20         else
21         {
22             int j=max(0,P-i+1);
23             while(j < len && i+j < len && S[j] == S[i+j])
24                 j++;
25             k=i;
26             nex[i]=j;
27         }
28     }
29 }
30 void getExtend()
31 {
32     int len1=strlen(T);
33     int len2=strlen(S);
34     int j=0;
35     while(j < len1 && j < len2 && T[j] == S[j])
36         j++;
37     extend[0]=j;
38     int k=0;
39     for(int i=1;i < len1;++i)
40     {
41         int P=k+extend[k]-1;
42         int L=nex[i-k];
43         if(L < P-i+1)
44             extend[i]=L;
45         else
46         {
47             int j=max(0,P-i+1);
48             while(i+j < len1 && j < len2 && T[i+j] == S[j])
49                 j++;
50             k=i;
51             extend[i]=j;
52         }
53     }
54 }
View Code
posted @ 2019-03-15 10:10  HHHyacinth  阅读(151)  评论(0编辑  收藏  举报