KMP算法

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

受有限自动机字符串匹配算法启示,利用前缀后缀原理,假设当前字符匹配长度为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的真后缀为空串P0
	k=0
	//递归求π[q],2<=q<=m
	for 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)。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值