KMP算法

来源:互联网 发布:如何安装cad软件 编辑:程序博客网 时间:2024/06/05 17:11

在朴素字符串匹配算法中,当匹配失败时,位移加一,也就是模式向后滑动一位,效率较低。我们能否在匹配失败时,利用已有的匹配信息(如当前文本的匹配位置,模式已经匹配的长度等)将模式向后滑动尽可能远的距离呢?

受有限自动机字符串匹配算法启示,利用前缀后缀原理,假设当前字符匹配长度为q(P[1..q]),在匹配P[q+1]时失败了,这时只要找到一个满足Pk为Pq的真后缀的最大k值,我们就可以把模式向后滑动q-k个长度,即位移s’=s+(q-k)。所谓真后缀是因为当前在匹配P[q+1]时已经失败了,模式至少要向后滑动1个长度,也就是说0<=k<q。但是我们注意到s’也不一定是个有效的位移,因为如果此时P[k+1]!=P[q+1],这也是一个无效的位移。再注意到Pk的真后缀P的最长前缀,也是Pq的真后缀,所以我们可以进行递推,直到找到一个合适的k,满足P[k+1]==P[q+1]或k==0。k==0也就是将模式向后移动q个长度,此时匹配长度为0,又开始重新匹配。

模式P的前缀函数next函数的定义如下:

next[q] = max{k|k<q,且Pk为Pq的真后缀},显然next[1]==0。

计算前缀函数的伪代码如下:

Compute-Prefix-Function(P){π[1] == 0//P1的真后缀为空串P0k=0//递归求π[q],2<=q<=mfor q=2 to m    {//进入循环说明已经匹配了q-1个字符//且进入循环时有k==π[q-1]        while(k>0 and P[k+1] ≠P[q])        {        k =π(k)//找下一个真后缀        }        if(P[k+1] ==P[q])//说明找到了合适的k值       then k++        π[q] = k  }return π;}


利用计算好的前缀函数,仅扫描一遍文本进行字符串匹配的算法伪代码如下:

KMP-Matcher(T,P){q = 0 //已经匹配的字符数for 1=1 to n{while(q>0 and P[q+1] ≠T[i]){q =π[q]}if(P[q+1]==T[i])then q++if(q==m)        {             print ”pattern occurs with shift ”i-m             q =π[q]        }}}


算法的C++实现及测试代码如下:

#include <cstdlib>#include <iostream>#include <cstring>using namespace std;//next数组范围为[0..m-1] void Compute_Prefix_Function(const char* P, int* next, int m){next[0] = -1;int k = -1;for(int q=1; q<m; q++){//此时已经匹配了q个字符,P[0,q-1],并且k=next[q-1]; //即P[0,k]为P[0,q-1]的后缀 ,k=-1时,Pk为空串 while(k>-1 && P[k+1]!=P[q]){k = next[k];}if(P[k+1]==P[q]){k++;}next[q] = k;}}void KMP_Matcher(const char* T, const char*P){int n = strlen(T);int m = strlen(P);int next[m]; Compute_Prefix_Function(P,next,m);int q = -1;//已匹配0个字符 for(int i=0; i<n; i++){while(q>-1 && P[q+1]!=T[i])//进入循环说明P[0..q-1]已经匹配 {q = next[q];}if(P[q+1]==T[i]){q++;}if(q==m-1){cout<<"Pattern occurs with shift "<<i-m+1<<endl;q = next[q];}}}int main(int argc, char *argv[]){const int Max_Length = 1000;char T[Max_Length];char P[Max_Length];while(gets(T)){gets(P);KMP_Matcher(T,P);cout<<"next case:"<<endl;}    system("PAUSE");    return EXIT_SUCCESS;}

补充说明:

(1)前面叙述的时候,我们假设数组下标是从1开始的,P0表示空串,在我们实际实现中,数组下标是从0开始的,我们只需简单地用-1表示空串即可,但是我们应该注意到当q==m-1是就表示找到了一个完整的匹配。

(2)如果你觉得上述代码不是很好理解,可以参考算法导论(第二版)P568-P573,多琢磨琢磨,你也可以参考严蔚敏的数据结构(C语言版)的第四章,那里提供了另外一种讲解思路,但是原理是一致的。

(3)算法的预处理时间为O(m),匹配时间为O(n)。



原创粉丝点击