e x k m p exkmp exkmp 求解的问题:对于给定的主串 S S S ,和模式串 T T T ,求出主串 S S S 的所有后缀与模式串 T T T 的最长公共前缀长度。
K M P KMP KMP 求解的问题是在主串 S S S 中模式串T出现的次数和位置, 扩 展 K M P 扩展KMP 扩展KMP 求解问题包含了 K M P KMP KMP 求解的问题,因为主串 S S S 中与模式串 T T T 的最长公共前缀长度等于 ∣ T ∣ |T| ∣T∣ 的后缀就是 K M P KMP KMP 所求。
e x k m p exkmp exkmp 算法求解过程实际是求两个数组的值:
n e t [ i ] net[i] net[i] : 模式串 T T T 的后缀 T s u f ( i ) Tsuf(i) Tsuf(i) 与模式串 T T T 本身的最长公共前缀长度 (net指next,C++11中next为保留字)
e x t e n d [ i ] extend[i] extend[i]:主串 S S S 的后缀 S s u f ( i ) Ssuf(i) Ssuf(i) 与模式串 T T T 的最长公共前缀长度
exkmp算法采用 递推求解 , e x t e n d [ i ] extend[i] extend[i] 的值可以由 e x t e n d [ 0.. i ] extend[0..i] extend[0..i] 和 n e t net net 数组的值求得。
设:主串 S S S ,长度 N N N ,模式串 T T T ,长度 M M M ,下标均从 0 0 0 开始。
假设 e x t e n d [ 0.. i ] extend[0..i] extend[0..i] 和 n e t net net 数组已知,现在正在求解 e x t e n d [ k ] extend[k] extend[k] 的值
设最远匹配位置为 p p p (即主串 S S S 下标分别从 0 0 0 ~ K + 1 K+1 K+1 开始与模式串T每次下标从 0 0 0 开始进行匹配可以到达的主串 S S S 的最大下标),则 p = m a x ( i + e x t e n d [ i ] − 1 ) p = max( i + extend[i]- 1 ) p=max(i+extend[i]−1) , i ∈ [ 0 , k − 1 ] i\in[0,k-1] i∈[0,k−1] (匹配起始位置 + + + 匹配长度 − - − 1 1 1 = = = 匹配末端位置)
设 j j j 为 m a x max max 取得最大值的下标,即
根据 e x t e n d [ j ] extend[j] extend[j] 的含义得到:
( p ≥ k ) (p\geq k) (p≥k) 时 上式两边同时删去 [ j . . k − 1 ] [j..k-1] [j..k−1] 得到:
设 l = n e t [ k − j ] l = net[k-j] l=net[k−j] ,根据 n e t net net 数组的定义有:
是 n e t [ k − j ] net[k-j] net[k−j] 的原因是 (1) 式中 T T T 的下标是从 k − j k-j k−j 开始的。
- C a s e 1 : Case 1: Case1:
若 p − j > k − j + l − 1 p - j > k - j + l - 1 p−j>k−j+l−1( (1) 式 T T T 右端下标 > > > (2) 式 T T T 右端下标 ),
即 p > k + l − 1 p > k + l -1 p>k+l−1 时 结合 (1)、(2) 式有:
所以此时 e x t e n d [ k ] = l extend[k] = l extend[k]=l
- C a s e 2 : Case 2: Case2:
若 p − j ≤ k − j + l − 1 p - j \leq k - j + l - 1 p−j≤k−j+l−1( (1) 式 T T T 右端下标 ≤ \leq ≤ (2) 式 T T T 右端下标 ),
即 p ≤ k + l − 1 p \leq k + l -1 p≤k+l−1 时 结合 (1)、(2)式有:
但从 S [ p + 1 ] S[p+1] S[p+1] 开始就不确定了
此时从主串 S S S 从下标 p + 1 p + 1 p+1 开始,模式串 T T T 从下标 p − k + 1 p - k + 1 p−k+1 开始进行匹配,
e x t e n d [ k ] = l + 额 外 匹 配 长 度 extend[k] = l + 额外匹配长度 extend[k]=l+额外匹配长度
最后更新 j j j , p p p 的值, j j j 的值直接等于 k k k ,因为最远匹配位置明显超过 p p p 了。
上面算法推导中有个小前提: p ≥ k p \geq k p≥k
当 p < k p < k p<k 时,必定有 p ≤ k + l − 1 p \leq k + l -1 p≤k+l−1,所以可以放到 C a s e 2 Case 2 Case2 里面,只不过此时的匹配起始位置为:主串 S S S 从下标 k k k 开始,模式串 T T T 下标从 0 0 0 开始。
n e t net net 数组求解: n e t net net 数组可以看做是 以 T T T 为主串, T T T 为模式串的一个特殊的 e x k m p exkmp exkmp,所以可以用上面的方法求解。将上面算法过程中的 e x t e n d [ i ] extend[i] extend[i] 用 n e t [ i ] net[i] net[i] 替换就行了。
因为 需要用到 n e t [ k − j ] net[k - j] net[k−j] 而 k − j ≤ k k-j \leq k k−j≤k,当且仅当 j = 0 , k = 1 j=0,k=1 j=0,k=1 时取等号,所以先把net[1]暴力求解就好。
求解 n e t net net 数组的代码:
void getNet(string& t){
int len = t.length(),j=0,p,l;
net[0] = len;
while(j<len&&t[1+j]==t[j]) j++; //求 net[1]
net[1] = j;
j = 1;
for(int k=2;k<len;k++){
p=j+net[j]-1;l=net[k-j];
if(p<=k+l-1){ // Case 2
int i=p>=k?p-k+1:0; //p是否大于等于k,i为从k开始的匹配长度
while(k+i<len&&t[k+i]==t[i]) i++;
net[k]=i;
j=k; //更新j
}
else net[k]=l; // Case 1
}
}
求 e x t e n d extend extend 数组的代码:
void getExtend(string& s,string& t){
int ls = s.length(),lt =t .length(),j=0,p,l;
while(j<ls&&j<lt&&s[j]==t[j]) j++; //求 extend[0]
extend[0] = j;
j = 0;
for(int k=1;k<ls;k++){
p=j+extend[j]-1;l=net[k-j];
if(p<=k+l-1){ // Case 2
int i=p>=k?p-k+1:0; //p是否大于等于k,i为从k开始的匹配长度
while(k+i<ls&&i<lt&&s[k+i]==t[i]) i++;
extend[k]=i;
j=k; //更新j
}
else extend[k]=l; // Case 1
}
}