字符串匹配算法KMP实现

具体原理参照《算法导论》,这里做了一点小修改。时间复杂度 O(n+m)

个人感觉,BF算法是当当前不匹配时同时回溯了匹配字串与模式字串,于是时间复杂度是O(n*m),而KMP算法改进的地方就是两点:

1.匹配字串指针不回溯,但模式字串指针会回溯。

2.种用模式字串的特点确定回溯的方式。

   假设匹配字串s指针为i, 模式字串t指针为p,当s[i]!=t[p]时,这时我们就要回溯p指针了。但是回溯到哪呢?这就是要求next[]的原因了。因为如果当前匹配字串与模式字串部分匹配了,那么匹配部分的信息就在模式字串中了,明显p前面的字串匹配成功了,而这个信息就在我们的模式字串中!如果我们先前就算好,我就可以知道,在哪不匹配时,我应把指针回溯到哪个位置,这样就不用同时回溯两个指针了。

说说Next[]怎么求的,怎么定义的。

    下面改编自《算法导论》:

    已知一个模式P[0,...,m-1],模式P的前缀函数是函数next:{0,...,m-1}->{0,...,m-1},满足:

                                        next[q] = max{k+1:k<q 且 P(k) 是 P(q)的后缀}

注意,这里的(k+1)是一个匹配长度,因为这里把所有下标都算回0开始了,所以后面会出现k=0时,next[2] = 1的情况,其实是一样的,只要把(k+1)当成是一个匹配长度就可以了 。这段话很容易理解,而且如果给你一个模式字串,你很容易就能把next求出来。怎么说?看下面例子(还是来自己导论。。。)

模式字串P = “ababaca”

首先,初始化next[0] = 0,第一个字符嘛,前面就没有东西可以和它匹配了,所以为匹配长度为0;

(1)当 q = 1,那么k = {0}, 判断P(k) 是 P(q)的后缀吗?

        P(q):    ab

        P(k):      a

    显然不是。。。那么next[1] = 0;

(2)当 q = 2, 那么k = {0,1}, 判断P(k) 是 P(q)的后缀吗?

      P(q):        aba

      P(k=0):        a       是的显然,next[2] = 1;

----------------------------------------------------------------------

     P(q):        aba

     P(k=1):      ab        ,此时不是

    

(3)当 q = 3, 那么k = {0,1,2}, 判断P(k) 是 P(q)的后缀吗?

      P(q):        abab

      P(k=2):      aba     此时不是

-------------------------------------------

     P(q):        abab

     P(k=1):        ab     next[3] = 2;//可以看成是k+1。

.....

下面同理可得,

最后的next[]:   0,0,1,2,3,0,1

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

上面是手动计算next的,算起来很简单,毕竟大脑比计算机强多了。如果用程序计算出这个next则不是这样的。在上面,我们是每次都判断当前P(k)是否是P(q)的后缀的,因为模式字串实际中比匹配字串要短,所以,你完全可以用BF的方式去算出这个next来的。但这里并没用BF来算,这也是KMP的改进之一。具体怎么算,还是看代码来得直接。

获取模式值的函数:这里next[]的下标从0开始,里面的值最小也是0,不是-1哦!

void getNextVal(int next[], int n, const char *str)
{
	int k = 0;
	int p = 1;

	next[0] = 0;//初始化
	while (str[p])
	{
		while (k > 0 && str[k] != str[p])//当前不匹配,回溯模式串,直到匹配或者k==0
			k = next[k-1];//k 是不断减小的,直到为0,而且不可能小于0,从大到小正好就是返回匹配的最大的(k+1),前辈们真是太牛了!
		if (str[k] == str[p])
			k += 1;       //如果当前匹配,k自加1!
		next[p] = k;//保存k的值,注意,这里的K已经加过一了!因为当前K就是最大的,从上面可以看出来,是从后面往前面找的。
		++p;
	}
}

字符串匹配KMP:

应该发现,模式值的求取与KMP算法很像!!那是因为模式值的求取本身也是一个字符串匹配的过程!

bool KMP(const string str, const string t, const int next[],int n)//参数1是匹配字串,参数1是模式字串,参数3是模式值,参数4是模式值个数,也就是模式字串长度
{
	int p = 0;//模式字串指针
	int i = 0;//匹配字串指针
	while (str[i])
	{
		while (p>0 && t[p] != str[i])//not match now
			p = next[p-1]; 当前不匹配,就回溯模式串中已经匹配的部分,所以p-1。退出后要么p==0,要么就是匹配成功
		if (t[p] == str[i])//
			p += 1;
		if (p == n)
		{
			cout << "match:"<<i-n+1<<endl;//打印匹配起始位置
			return true;
		}
		++i;//改变i,p由上面改变,因为p的改变有两种情况,当前p与i所指一样时,p++,i++,而不一样时,不断的变换p,此时i不用变,就是上面的while
	}
	cout << "not match"<<endl;
	return false;
}

打印模式值:

void printNextVal(int next[], int n)
{
	for (int i = 0; i < n; ++i)
		cout<<next[i]<<" ";
}



测试:

int main(void)
{
	string str="abcdaa";
	int *next = new int[str.size()];
	getNextVal(next, str.size(),str.c_str());
	printNextVal(next,str.size());
	KMP("abcdeeabcdaaeeabc",str, next,str.size());
	
	return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值