KMP算法

KMP算法的特点:相较于BF算法,在KMP算法中 当主串位置对应的字符与子串位置对应的字符不相同时,主串的查找位置不会进行回退,而子串查找的位置会回退到一个特定的位置上。

这个特定的位置该如何求呢?或者说是什么呢? 

 对于这个特定位置,KMP算法规定 子串中每个位置都有一个要回退的特定位置,并将这些特定位置单独存放到一个数组中,我们称这个数组为next数组 

 这些特定位置的求法:  KMP算法规定:子串中第一个元素和第二个元素的对应的next数组的值(即第一个元素如果没有匹配成功,回退的位置;第二个元素当没有匹配成功,要回退的位置)分别为 -1  ,,,0   【此处在其他情况下,也被认为分别是0,,,1; 这些不会影响后续的运算逻辑,后续对应的next数组存放的元素都比 -1 ,,,0那种要 大1】                                                    { 下文中我们是带入的子串前两个元素对应的next数组的值分别为 -1  ,,,0的情况 }

求法:记子串为sub 数组;  KMP算法规定: 在sub[ j ] 之前寻找两个以sub[ 0 ] 的字符为开头,以 sub[ j - 1 ]的元素为结尾的两个完全相同的字符串【这两个字符串是有规定的,第一个字符串必须从sub[ 0 ] 开始。  第二个字符串必须在sub[ j - 1 ] 结束; 】,这个字符串的长度就是next[ i ] 的值。    如果找不到两个以sub[ j - 1 ] 开头,以sub[ j - 1 ] 结尾的两个数组,则记该next[ i ] = 0;

eg:  

当 j 在下标为3的位置处; sub[ 0 ] = ' a ' ;sub[ j - 1 ] = ' c '; 因此我们就在sub[ 0 ] 到sub[ j - 1](包含sub[ 0 ] 和sub[ j -1 ])寻找以’a‘开头以’c‘结尾的两个相同的字符串, 显然从sub[ 0 ] 到 sub[ j - 1 ] 间没有这样的两个字符串,因此 next[ j ] = 0; 

从sub[ 0 ] 到sub[ j - 1 ] 寻找两个完全相同的字符串的时候,第一个字符串的尾端是可以和第二个字符串的首段是可以重合的

eg:sub[ 0 ] = 'a' ; sub[ j - 1 ] = ' a ' ;因此我们要在sub[ 0 ] 到 sub[ j - 1 ] 间寻找以a开头,以a结尾的两个字符串; 即sub[ 0 ]...sub[ 3 ] 和 sub[ 3 ]...sub[ 6 ] 这两个是以'a'开头,以'a'结尾的相同的字符串;长度为4。 因此next[ j ] = 4;

通过上述方法,我们可以依次求出该子串对应的next数组的值。如下

现在我们知道了如何求next数组,接下来我们观察其数学关系,求出通式

已知: next[ j ] = k;    因此肯定会有sub[ 0 ]...sub[ k - 1 ] = sub[ x ]...sub[ j  -  1 ];  又这两个字符串完全相同,所以有 k - 1 - 0 = j - 1 - x  ==>   x = j - k ;  因此上文的等式可以替换为sub[ 0 ]...sub[ k -1 ] = sub[ j - k ]...sub[ j  -  1 ] ;   

基于这个条件,衍生了两种情况:即sub[ k ] 与sub[ j ] 的关系

1. 如果( sub[ k ] == sub[ j ] || k == -1 )  为真  ( k == -1 时也进入的原因,在情况2中会声明)

那么sub[ 0 ]...sub[ k - 1 ] + sub[ k ] = sub[ j - k ]...sub[ j - 1 ] + sub[ j ] 为真,即 sub[ 0 ]...sub[ k ] = sub[ j - k ]...sub[ j ] 为真,KMP算法规定此时的 next[ j + 1 ] = k + 1;

2. 如果sub[ k ] != sub[ j ] 为真

那么sub[ k ] 会回退到一个特定的位置( 这个位置为 next[ k ] ) , 直到sub[ k ] == sub[ j ] 为真;但是此时还有一种情况,如果k = 0;且sub[ k ] != sub[ j ] 仍为真,那么k = next[ k ]时,我们可以注意到此时的next[ k ] = -1; 也就是说k会回退到 下标为 - 1 的位置,在这种情况下,如果我们在后续仍判断  sub[ k ] == sub[ j ] 为真还是假,那么就会访问到 sub[ -1 ] ,造成越界访问,因此我们的在第一种情况下的判断条件应为 ( k== -1 || sub[ k ] == sub[ j ] ) 为真 

我们现在已经知道,已知 next[ j ] = k的情况下,如何求 next [ j + 1 ] 了。如果我们令 j = j - 1 ;就是知道了 已知 next[ j -1 ] = k 的情况下,如何求 next [ j ] 的方法

下文则是KMP算法的代码实现

算法主体:

/*
* str为主串
* sub为子串
* sup为主串被查找的位置
*/
int KMP(char* str, char* sub, int sup)//如果无法被查找则返回 -1 如果能找到返回 1 如果找不到返回 0
{
	int i = sup;
	int j = 0; 
	int lenstr = strlen(str);//str长度
	int lensub = strlen(sub);//sub长度
	if (str == NULL || sub == NULL)
		return -1;
	if (lenstr == 0 || lensub == 0)
		return -1;
	int* next = (int*)malloc(strlen(sub) * sizeof(int));//创建一个与子串长度相同的next数组
	if (next == NULL)//确定如果程序不返回100时next数组的空间开辟成功
		return 100;
	To_next(sub, next);//求出next数组
	while (i < lenstr && j < lensub)//保证i和j的合法
	{
		if ( j == -1 || str[i] == sub[j])
		{
			i++;
			j++;
		}
		else
		{
			j = next[j]; //当不匹配时,j回到一个特定的位置
		}
	}
	if (j >= lensub)
	{
		return 1;
	}
	else
	{
		return 0;
	}
}

求next数组的代码:

/*
* sub为子串
* next为next数组,存放子串每个元素要回退的特定位置
*/
void To_next(char* sub, int* next)
{
	next[0] = -1;
	next[1] = 0; //初始化前两元素
	int lensub = strlen(sub);
	int j = 2; //从下标为2的位置开始,即从第三个元素开始
	int k = next[1];//k为sub[1]对应的next值
	//上文相当于知道了next[ j - 1 ] = k; 接下来我们依次求 next[ j ] 
	while (j < lensub)
	{
		if (k == -1 || sub[k] == sub[j])
		{
			next[j] = k + 1;
			j++;
		}
		else
		{
			k = next[k]; //k进行回退,直到sub[k] = sub[j]或者 k = -1
		}
	}
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值