KMP算法

       近日查看上届“恒生杯”初赛的题目,有一道题需要用到KMP算法。KMP算法在《数据结构》课上学过,但是当时只听了个一知半解,也没有自己动手实践。写这篇文章既算是对自己以前学习态度的一种反思,也是填自己以前挖的大坑。

声明:本文参考了july的《从头到尾彻底理解KMP

            以及 http://www.cnblogs.com/dolphin0520/archive/2011/08/24/2151846.html

       KMP算法是一种改进的字符串匹配算法,由D.E.Knuth与V.R.Pratt和J.H.Morris同时发现,而Knuth正是《The Art of Computer programming》的作者高德纳。不过在介绍KMP算法之前,首先介绍一种大家都能想的到的字符串匹配算法——BF算法。

     1.BF算法

       BF即Brute Force,闻名知意,BF算法是一种低效的暴力算法。此算法的过程大家也很容易想到,将文本串S的第一个字母和模式串P的第一个字母相比较,如若相等,则比较S第二个字母和P第二个字母;否则,比较S第二个字母和P的第一个字母。依次比较下去,就能得到首次匹配成功的位置,或者S到达末尾,匹配不成功。该方法的核心是文本串S的下标在比较不相等时需要回溯,而模式串P下标归零。

      举例说明:文本串S: abababc

                        模式串P: ababc

     i表示文本串S的下标,j表示模式串P的下标。


 i = 0                    i = 1             i++,j++直到...         i = 4           此时s[i] != p[j],i回溯,j归零         i = 1

abababc            abababc                                     abababc                                                     abababc

ababc                ababc                                         ababc                                                           ababc

 j = 0                    j = 1                                            j = 4                                                            j = 0


  s[i] != p[j]...回溯         i = 2                     i = 2                 i = 3                    i = 4                    i = 5 (成功)

                                  abababc            abababc            abababc            abababc            abababc

                                      ababc                ababc                ababc                ababc                ababc

                                     j = 0                      j = 1                 j = 2                   j = 3                   j = 4


BF算法:

int BFMatch(char* s,char* p)
{
	int i = 0;
	int j = 0;
	int slen = strlen(s);
	int plen = strlen(p);

	while(i < slen && j < plen)
	{
		if(s[i] == p[j])
		{
			i++;
			j++;
		}
		else	//i回溯,j归零
		{
			i = i - j + 1;
			j = 0;
		}
	}
	if(j == plen)		//模式串到尾部,匹配成功
		return i - j;
	else return -1;		//匹配失败
}

     2.KMP算法

     KMP算法则是省去了BF算法的一些步骤。每次匹配不成功时,保持s下标i不变,改变p下标j,这样使时间复杂度由O(mn)降到了O(m+n)。为了确定p下标j的值,引入next数组。next[j]表示p[0...j-1]中的前后缀最长公共元素长度。

    next[]严谨定义如下:
    next[j] = -1 , j = 0
    next[j] = max(k) , p[0 ... k-1] = p[j-1-k ... j-1] 0< k < j


    所以,当p为ababc,对应next[]数组为:
    p       :  a  b  a  b  c
    next  : -1  0  0  1  2

   如果在匹配过程中,出现s[i] != p[j] 情况,如果next[j] >= 0,保持i不变,j = next[j];如果next[j] == -1,则令i++,j++。匹配过程如下,对比上文BF算法过程,可知KMP算法省去了哪些步骤。


  i = 0                         i = 1                        i = 2                        i = 3                         i = 4

a b a b a b c          a b a b a b c          a b a b a b c          a b a b a b c         a b a b a b c

a b a b c                a b a b c                a b a b c                a b a b c               a b a b c

  j = 0                        j = 1                         j = 2                        j = 3                         j = 4(j = next[j] = 2)


  i = 4                        i = 5                         i = 6 (匹配成功) 

a b a b a b c          a b a b a b c          a b a b a b c

      a b a b c                a b a b c                a b a b c

  j = 2                        j = 3                        j = 4


  KMP算法巧妙地利用了模式串p的前后缀相同部分,减少了没有必要的比较。但是next数组该如何求取呢?我们可以利用递推的方法。

  已知next[0] = -1,next[j] = k 即 p[0...k-1] == p[j-1-k ... j-1] .

  若p[k] == p[j],即 p[0...k] == p[j-1-k .. j],则next[j+1] = k+1 .

  若p[k] != p[j],把它看作后缀和前缀在进行模式匹配,根据上文知,匹配失败时,k = next[k] .如果一直匹配失败直到k == -1,那么前后缀最长公共元素长度为0,令next[j+1] = 0 .

  注意:求取next[1]时,虽然p[0] == p[0],但是大家都令next[1] = 0。如果next[1] = 1的话,KMP匹配会陷入死循环。

求取next数组:

void GetNext(char* p,int next[])
{
	int plen = strlen(p);
	int k = -1;
	int j = 0;

	next[0] = -1;
	while(j < plen - 1)
	{
		if(k == -1 || p[j] == p[k])
		{
			k++;
			j++;
			next[j] = k;	//相当于next[j+1] = k+1
		}
		else
			k = next[k];
	}
}

KMP算法:

int KMP(char* s,char* p)
{
	int slen = strlen(s);
	int plen = strlen(p);

	int i = 0;
	int j = 0;
	while(i < slen && j < plen)
	{
		if(j == -1 || s[i] == p[j])
		{
			i++;
			j++;
		}
		else
			j = next[j];	//i不变,j改变
	}
	if(j == plen)	//匹配成功,返回s中匹配初始坐标
		return i - j;
	else return -1;
}



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值