字符串的模式匹配

0) 引论

这里主要想讨论一下字符串的模式匹配,主要是KMP算法。

假设我们有一个字符串S,称之为原始串;另一个字符串T,称之为模式串;字符串匹配是指找出原始串S中是否含有模式串T,如果含有,则返回S中第一个匹配项的位置;

例如S=“abcabcabdef”;T=“abcabd”;那么我们可以得到S中含有模式串T,我们返回S中第一个匹配项的位置,即3。


1) Brute-Force算法

暴力算法,应该算是最朴素的算法了,大概小学生也能想出这个解决方法;BF算法的思想是:从S的第一个字符与T的第一个字符开始比较,如果相等,则进行下一个字符的比较,直到遇到不相等的项;这时我们把T的第一个字符与S的第二个字符相比较,重复上面的过程,知道找到匹配的项为止,或者返回一个值,显示没有匹配的项。

对上面的例子:

abcabcabdef

abcabd;

可知匹配不成立,所以要把T向右移一位,即

abcabcabdef

    abcabd;

还是不成立,继续右移一位

abcabcabdef

        abcabd;

还是不成立,继续右移一位

abcabcabdef

            abcabd;

这时我们得到了匹配,返回位置3。

由此可见,BF算法就是一个类似于穷搜的算法,这个算法的时间复杂度为O(strlen(S)*strlen(T))。空间复杂度为O(1)

代码实现:

# include<stdio.h>
#include<string.h>
#include<windows.h>
int BruteForce(char *S,char *T)
{
	unsigned int i=0,j=0;
	int k=0;
	while(i<strlen(S)&&j<strlen(T))
	{
		if(S[i+k]==T[j])
		{
			k++;
			j++;
		}
		else
		{
			i++;
			j=0;
			k=0;
		}
		if(S[i+strlen(T)-1]==T[strlen(T)-1])
		{
			return i;
			break;
		}
	}

}
void main()
{
	char *S,*T;
	S = "abcdabcabdef";
	T = "abcabd";
	int BF;
	BF = BruteForce(S,T);
	printf("%d\n",BF);
	system("pause");
}

2) KMP算法

上面介绍的Brute-Force算法很简单也相当好理解,但是作为一个受过多年高等教育的人来说,用上面的方法简直是一种侮辱,因此我们需要更加复杂,更有挑战的算法(纯属玩笑话)。其实最主要的原因是上面的算法耗时,做了一些无用的功。每次把模式串向右移动一步并不够,例如上面的例子我们可以很容易的得到应该把T向右移动3步比较合适,那么怎么让计算机能够做到的,那么我们就需要KMP算法。同时我们也可以看到上面的算法没有利用T中已经匹配了的字符的一些信息。

KMP算法由D.E.Knuth与V.R.Pratt和J.H.Morris同时发现的。KMP算法的与BF算法的最大的不同就在于“失配”的处理上,BF算法当遇到失配时,做法是把模式串向右移动一位;而KMP算法则是向右移动更多的位,这个由一个next数组控制,已达到加速的目的。

对上面的例子:

abcabcabdef

abcabd;

我们可以得到在位置5处,发生了失配,也就是S[5]!=T[5]。那么对于KMP算法,我们需要将T向右移位,也就是说要另S[5]==T[next[5]];更通用的表述是当S[i]!=T[j]时,令j=next[j],使S[i]==T[next[j]],也就是说相当于将T向右移动了j-next[j]>=1位。因此KMP算法的核心就是next数组。next数组只与模式串T的本身性质有关,而与原始串S无关。

下面我们来看next数组怎样求:


KMP算法中,如果当前字符匹配成功,即S[i]==T[j],令i++j++,继续匹配下一个字符;如果匹配失败,即S[i] != T[j],需要保持i不变,并且让j = next[j],这里next[j] <=j -1,即模式串T相对于原始串S向右移动了至少1(移动的实际位数j - next[j]  >=1)。

因此要应用KMP算法,我们首先可以利用模式串T来得到相应的next数组。

a) next[0]=-1: 任何串的首字符的next值为-1;

b) next[j]=-1: 模式串T中下标为j的字符,如果与首字符相同,且j的前面的1—k个字符与开头的1—k个字符不等(或者相等但T[k]==T[j])(1k<j)。

c) next[j]=k:  模式串T中下标为j的字符,如果j的前面k字符与开头的k个字符相等,且T[j] != T[k] 1k<j

d) next[j]=0:  除了上面的情况外其他的情况。

next数组值的意义

设在字符串S中查找模式串T,若S[m]!=T[n],那么,取T[n]的模式函数值next[n],

a) next[n]=  -1 表示S[m]和T[0]间接比较过了,不相等,下一次比较 S[m+1]和T[0]。

b) next[n]=0 表示比较过程中产生了不相等,下一次比较 S[m] 和T[0]。

c) next[n]= k >0 但k<n, 表示,S[m]的前k个字符与T中的开始k个字符已经间接比较相等了,下一次比较S[m]和T[k]是否相等。

int *Next(char *T,int *next)
{
	int j = 0, k = -1;
	next[0] = -1;
	while ( T[j] != '\0' )
	{
		 if (k == -1 || T[j] == T[k])
			{
				++j; ++k;
				if (T[j]!=T[k])
					next[j] = k;
				else
					next[j] = next[k];
			 }
		  else
			  k = next[k];
	}
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值