BF算法和KMP算法的简介

BF算法和KMP算法是两种比较著名的模式匹配算法(个人觉得可以说为字符串匹配)。本文对这两种算法进行介绍。

1、  BF算法

这是最简单直观的模式匹配算法。子串(T)从主字符串(S)的指定位置(假定为开头)开始对比,假设i和j分别为主串(S)和子串(T)中下一个位置。

如果S[i].ch和T[j].ch相等,则两个字符串里的i和j都加1,也就是指示串中的下一个位置,并继续进行比较;

如果S[i].ch和T[j].ch不相等,则指针直接退回主串的下一个字符(i=i-j+2)再重新和子串的第一个字符(j=1)比较。

上述解释中比较难理解的应是i=i-j+2,i表示主串的移动距离,j表示子串的移动距离,在未出现S[i].ch和T[j].ch不相等时,i与j应该相等,所以i-j也就是退回到i开始的第0个字符,而加上2也就等价于将i移动到i最初开始的第2个字符,因为文中是以1表示i开始的字符,而第2个字符也就等价于i开始字符之后的字符。

比如说主串为abcdefghi,子串为abcdf,则当i=j=5时,S[5].ch和T[5].ch不相等,则将i退回开始字符的之后字符(即第2个字符)。

主串              子串                             主串                         子串

a b c de f g h I   a b c d f                  a b a b c a b c a c  b a b     a b c a c

1 2 3 4 5 67 8 9   1 2 3 4 5                   1 2 3 4 5 6 7 8 9 10 11 1213      1 2 3 4 5

第一次匹配:   a b c d e f g h I            a b a b c a b c a c  b a  b

                       i=5                   i=3

                         a b c d f                  a b c a c

                       j=5                   j=3

 

第二次匹配:   a b c d e f g h I       a b a b c a b c a c  b a  b

                  i=2                 i=2

      a b c d f            a b c a c

                  j=1                 j=1

第三次匹配:   a b c d e f g h I            a b a b c a b c a c  b a  b

                   i=3                             i=7

        a b c d f                  a b c a c

                   j=1                             j=5

                     .                             .

                     .                             .

                     .                             .

第九次匹配:   a b c d e f g h I          第6次匹配:a b a b c a b c a c  b a  b

                             i=9                                      i=11

        a b c d f                        a b c a c

                             j=1                                      j=6

       可以说,BF算法比较粗暴直观,它直接从第1字符开始一个一个字符地比较主串和子串的字符,如果不一样就回溯到开始的第2个字符(也就是i-j+2)开始比较,而子串是回到其第1个字符(即j=1),但其时间复杂度比较高。

 

# include
# include

int Wf_Bf(char *Smain,char * Sson, int pos,int Smain_len, int Sson_len)
{
	int i = 0;
	int j = 0;

	if(0==Smain_len || 0==Sson_len){
#if 0
		int	Smain_len = strlen(Smain);
		int	Sson_len = strlen(Sson);
#endif
		Smain_len = strlen(Smain);
		Sson_len = strlen(Sson);
	}

	if(NULL==Smain || NULL == Sson )
		return -1;
	if(0Sson_len){
			return i - Sson_len;
		}
		else {
			return 0;
		}
#endif


}

int main()
{
#if 1
	char *Smain = "ababcabcacbab";
	char *Sson = "abcac";
#else
	char *Smain = "abcdefghI";
	char *Sson = "abcdf";
#endif
	int flag = Wf_Bf(Smain,Sson,0,0,0);

	if(-1==flag)
		printf("fail to match\n");
	else
		printf("match successfully, the address is %c\n", Smain[flag]);

	return 0;
}

 

2.KMP算法

       KMP算法是由Knuth、Morris和Pratt同时设计实现,故简称为KMP算法,但我觉得其可简称为  看毛片算法  ,让人印象更加深刻。

       看BF算法,可以发现其主串的i指针总是反复地回溯到开始的地址,这会花费很多的时间,而KMP便不会回溯i指针。

       现介绍一下算法的思想:假设主串为:“s1s2…sn”,子串为“t1t2…tm”,为了避免主串i的重复回溯,KMP便得解决当主串第i个字符与子串第j个字符不相等时,主串中第i个字符应与子串中哪个字符进行比较?

假设此时应与子串中第k(k<j)个字符继续比较,则子串中前k-1个字符的子串必须满足下式,且不可能存在k’>k满足下式:

                     “t1t2…tk-1”= “si-k+1si-k+1…si-1”

当对比到第k个字符时,子串中的前k-1个字符应与主串中从i往前数k个字符是一样的,而已经得到的“部分匹配”的结果是

              “tj-k+1t j-k+2…tj-1”= “si-k+1si-k+1…si-1”

而从子串的j-1处可以知道子串中的前k-1个字符应与主串中从i往前数k个字符是一样的,所以可以得出:

                                   “t1t2…tk-1”= “tj-k+1t j-k+2…tj-1”

       从上述公式便可以知道,想要求出k的具体数值,便是求出子串的对应长度(k)内符合上述公式的字符长度。因此可以导出下述公式:

《BF算法和KMP算法的简介》

根据上述公式可以求得next[j]的数值,用文字描述如下:

假设以指针i和j分别指示主串和子串中正待比较的字符,令i的初值为pos,j的初值为1。若在匹配工程中si=tj,则i和j分别增1,否则i不变,j退到next[j]的位置再比较,若相等,则指针各自增1,否则j再退到下一个next值的位置再比较,依次类推,直至下列两种可能:

一种是j退到next值(next[next[…next[j]…]])时字符比较相等,则指针各自增1,继续进行匹配;

另一种是j退至值为0(即模式的第一个字符“失配”),则此时需将子串继续向右滑动一个位置,即从主串si+1起和子串重新开始匹配。

   j

   1   

   2   

   3   

   4   

   5   

   6   

   7   

   8   

  子串  

   a

   b

   a

   a

   b

   c

   a

   c

   next[j]   

   0

   1

   1

   2

   2

   3

   1

   2

通过上述便可获得KMP。

    然后便可进行KMP算法:开始对主串和子串进行匹配,当匹配过程中产生“失配”时,指针i不变,指针j退回至next[j]所指示位置重新进行比较,如果不匹配,则指针退回至next[next[j]]进行对比…直到指针j退至0时,指针i和指针j便需同时增1,也就是说如果主串的第i个字符和子串的第1个字符不等,则从主串的第i+1个字符起重新匹配。

       综上所述,可以发现KMP的关键点是next[j]的获取。

       由定义可知,

当j为1时,next[1] = 0

       假设next[j]= k,则说明子串中存在

                                          “t1t2…tk-1”= “tj-k+1t j-k+2…tj-1”

其中1<k<j,且不可能存在k’>k,而求next[j+1]可分为两个步骤:

(1) 如果 tk = tj,则表示存在

“t1t2…tk-1 tk” = “tj-k+1tj-k+2…tj-1 tj

因此可求得

next[j+1] = k+1,即next[j+1] = next[j] +1

(2)如果tk!= tj,便需要把求next值的问题看成是一个子串匹配的问题,整个子串既是主串又是子串,而在匹配过程中,t1= tj-k+1, t2= tj-k+2,。。。, tk-1 = tj-1,则当时将子串向右滑动直到子串中第next[k]个字符和主串中第j个字符相比较。直到有一个next[k] = k’,且tk’= tj则说明在主串中第j+1个字符前存在一长度为k’(即next[k])的最长子串,和子串中从首字符起长度为k’的子串相等,即

“t1t2…tk’-1”= “tj-k’+1t j-k’+2…tj-1” (1<k’<k<j)

也就等价于next[j+1]=k+1,即

                                          Next[j+1]= next[k] +1

与之类似的,如果tk’!= tj,则继续将子串右滑动与第next[next[k’]]字符进行匹配,若相等则+1处理,若不等继续下去直到next[…]的值为0,那时next的值便是1。

   j

   1   

   2   

   3   

   4   

   5   

   6   

   7   

   9   

   子串   

   a   

   b   

   a   

   a

   b   

   c

   a

   c

   next[j]   

   0

   1

   1

   2

   2

   3

   1

   2

这里以next[7]的值获取为例子对next[j]的求取进行说明。

 

第一遍: 1  2  3  4  5  6  7  8      j

         a b  a  a  b  c  a  c      子串

                  a  b a            子串的子串

获取next[7]便需对比t6和tnext[6]的数值,因为a!=c,所以子串向右滑动值next[3](也就是next[next[6]])

第二遍:1  2  3  4  5  6  7  8      j

         a  b  a  a  b  c  a  c      子串

                        a           子串的子串

因为t6!=t1,其中1的值为next[next[6]] = 1,而next[1] =0,所以next[7] = 1

小结:KMP算法就是主串的指针i只增不减,当i所指的字符与子串指针j所指的字符不等时,就将子串指针j的值改为next[j](也就是将指针向左移,将字符串整体向右移),再进行对比,直到i所指的值与j所指的值相等或者是next[j]的值为0,然后i+1。

因此,KMP的关键点是next[j]值的获取,而next[j]值便是通过对子串进行再一次的KMP算法的处理,即子串既是主串又是子串,

简单说:求next[j]时,先对比tj-1和tk的值(其中k=next[j-1]),

如果相等,则next[j] = k +1=next[j-1] +1;

如果不等,则再对比tj-1和tk的值(此时k=next[next[j-1]]

                如果相等,则next[j] = k+1

                如果不等,则再对比tj-1和tk的值(此时k=next[ next[  next[j-1]  ]  ]

……………………………………………………………

如此重复,直到next[…]的值为0或者存在一个k,使得tj-1和tk想等。

#include 
#include 
#include 
#include 

/*实现get—next*/
#if 0
int * Wf_Kmp_next(char *Sson,int Sson_len)
{
	int i = 0;
	int j = -1;
	if(0==Sson_len){
		Sson_len = strlen(Sson);
	}

	int * next = (int*)calloc(sizeof(int),Sson_len);
	next[0] = -1;


	while(i<(Sson_len-1)){
		if(-1==j || Sson[i]==Sson[j]){
			j++;
			i++;
			next[i] = j;
		}
		else 
			j = next[j];
	}
	return next;

}
#else
/*实现get_nextval*/
int * Wf_Kmp_next(char *Sson,int Sson_len)
{
	int i = 0;
	int j = -1;
	if(0==Sson_len){
		Sson_len = strlen(Sson);
	}

	int * next = (int*)calloc(sizeof(int),Sson_len);
	next[0] = -1;


	while(i<(Sson_len-1)){
		if(-1==j || Sson[i]==Sson[j]){
			j++;
			i++;
			if(Sson[i]!=Sson[j]) 
				next[i] = j;
			else next[i] = next[j];
		}
		else 
			j = next[j];
	}
	return next;

}
#endif

int Wf_Kmp(char *Smain, char *Sson,int pos, int Smain_len, int Sson_len)
{
	int i = 0;
	int j = 0;
	int * next = Wf_Kmp_next(Sson,Sson_len);

	if(NULL==Smain || NULL==Sson)
		return -1;
	if(0==Smain_len || 0==Sson_len){
		Smain_len = strlen(Smain);
		Sson_len = strlen(Sson);
	}
	if(0<=pos && pos <= Smain_len){
		i = pos;
		j = 0;
	}
	else 
		return -1;
//	while(i(Sson_len-1))	return i-j;
	else return 0;
}

int main(void)
{
	char *Smain = "acabaabaabcacaabc";

	char *Sson = "abaabcac";
#if 1
	  int flag = 0;
	  flag = Wf_Kmp(Smain,Sson,0,0,0);

	printf("the flag is %d, the c is %c\n",flag,Smain[flag]);
#else
	int * next = Wf_Kmp_next(Sson,0);
	int i = strlen(Sson);
	for(int j=0;j

PS:需要留意的是算法介绍里串的初始字符是1开始计数的,但在C中,其计数是以0开始的,所以0值最好改为-1。

    原文作者:KMP算法
    原文地址: https://blog.csdn.net/qwe15954250805/article/details/77970873
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞