字符串匹配BM算法学习

我们把被搜索的字符串称为文本text,待匹配的字符串称为模式串pattern。BM算法的核心思想就是两个,第一是坏字符,第二是好后缀,好后缀就是pattern与text从右往左连续匹配成功的子串,坏字符就是pattern与text从右往左第一个匹配失败的在text中的字符,如下:

对于坏字符和好后缀,无非就是不同的模式串移动规则,通过各自不同的移动规则,确定分别对于坏字符和好后缀模式串需要移动的位数,最终选择移动位数最大的进行移动即可。

一般来说,文本和模式串都是用数组来表示的,在字符串的匹配过程中,当出现不匹配时,需要进行模式串相对于文本的移动,我们简称为模式串的移动。具体移动方法如下:

下面具体讲解坏字符和好后缀的移动规则,假设文本为text[ i .. i+n ],pattern[ j .. j+m ]。

一. 坏字符移动规则

我们假设坏字符出现在text的 i + k 位置上(也就是pattern的 j + k 位置),我们记坏字符为 bad_char = text[ i + k ]。

现在我们在pattern[ j .. j + k - 1 ]中搜索bad_char(注意:在搜索bad_char的过程中,是从右向左进行匹配的,并且将第一次匹配成功时的位置作为结果返回

可能出现两种情况:

1) 坏字符没有出现在pattern[ j .. j + k - 1 ]中,此时直接将模式串移动到坏字符的下一个字符位置,相当于pattern移动(j + k) - (j - 1) = k + 1位。

2) 坏字符出现在pattern[ j .. j + k - 1 ]中,位置为pattern[ j + s ],那么pattern移动(j + k)-(j + s)= k - s位。

坏字符移动的思考:如果每次出现bad_char都需要进行一次O(m)时间复杂度的搜索,效率比较低,最好是出现了bad_char,O(1)时间就能确定模式串需要移动的位数。下面看看怎么找到这种优化的方法:

我们记bc[ 'a' ] = j + 1,那么模式串需要移动的位数就等于bad_char的位置减去bc[ 'a' ],即 (j + 4)- (bc[ 'a' ])= 3。

我们记bc[ 'b' ] = j + 3,模式串需要移动的位数(j + 4)- (bc[ 'b' ])= 1

当坏字符在模式串中找不到匹配时,统一移动到坏字符的下一个位置,所以我们记bc[ 'd' .. 'z' ] = j - 1。

模式串需要移动的位数为(j + 4)- (bc[ 'd' ])= 5位。现在再来观察这个bc数组我们能发现,bc[ ch ] 的

值就是字符ch在模式串中最右边出现的位置索引,因为还有一个字符c没有在bc数组里面确定值,根据发现,

可以假设bc[ 'c' ] = j + 4。现在有了bc数组,我们再来看一种情况:

这种情况,我们来计算模式串需要移动的位数,(j +3)-(bc[ 'c' ] ) = -1,这是一个小于零的值,意味着要对

模式串做负的移动,肯定不对,我们再观察,就可以发现这种情况同样直接将模式串移动到bad_char的下一个

字符位置就可以了。即移动的位数为(j + 3)- (j - 1) = 4位。

最终,我们可以确定优化坏字符移动的方法,首先针对模式串计算辅助数组bc[ ch ]:

1) 若ch出现在模式串中,则bc[ ch ]的值为ch在模式串中最右边出现的位置索引。

2) 若ch没有出现在模式串中,则bc[ ch ]的值为 j - 1,j为模式串第一个字符索引,从这里可以看出,

在求bc数组时,只需要关心模式串,即与文本无关。

3) 在通过bc数组计算模式串移动的位置时,需要注意,如果移动的位置为负值,则等同于

将模式串移动到坏字符的下一个位置。

二. 好后缀的移动规则

suffix[ i ]:表示从模式串的位置 i 开始向前扫描,看能够与整个模式串的后缀匹配的最大长度,我们把这个长度作为suffix[ i ]的值。下面用图解释一下就清楚了:

好了,有了suffix数组,接下来计算gs数组。gs[ i ]:i表示好后缀前一个字符,即坏字符的位置,gs[ i ]的值表示模式串需要移动的位数。

计算gs数组时分三种情况:

1)模式串中存在子串与好后缀匹配;

2)模式串中不存在子串与好后缀匹配,但是存在最大前缀与好后缀匹配;

3)模式串中即不存在子串与好后缀匹配,也不存在最大前缀与好后缀匹配。

 

三. 代码

int* CreateBC(char* pattern, int len)
{
	int* bc = new int[256];

	for(int i = 0; i < 256; ++i)
	{
		bc[i] = -1;
	}

	for(int i = 0; i < len; ++i)
	{
		bc[pattern[i]] = i;
	}

	for(int i = 0; i < 256; ++i)
	{
		if(bc[i] != -1)
		{
			cout << "bc[" << i << "] = " << bc[i] << endl;
		}
	}
	return bc;
}

int* CreateSuffix(char* pattern, int len)
{
	int* suffix = new int[len];
	suffix[len - 1] = len;

	for(int i = len - 2; i >= 0; --i)
	{
		int j = i;
		for(; pattern[j] == pattern[len - 1 - i + j] && j >= 0; --j);
		suffix[i] = i - j;
	}

	for(int i = 0; i < len; ++i)
	{
		cout << "suffix[" << i << "] = " << suffix[i] << endl;
	}

	return suffix;
}

int* CreateGS(char* pattern, int len)
{
	int* suffix = CreateSuffix(pattern, len);
	int* gs = new int[len];
	/*
	在计算gs数组时,从移动数最大的情况依次到移动数最少的情况赋值,
	确保在合理的移动范围内,移动最少的距离,避免失配的情况。
	*/

    //第三种情况
	for(int i = 1; i < len; ++i)
	{
		gs[i] = len;
	}

    //第二种情况
	for(int i = len - 1; i >= 0; --i) //从右往左扫描,确保模式串移动最少。
	{
		if(suffix[i] == i + 1) //是一个与好后缀匹配的最大前缀
		{
			for(int j = 0; j < len - 1 - i; ++j)
			{
				if(gs[j] == len) //gs[j]初始值为len, 这样确保gs[j]只被修改一次
				{
					gs[j] = len - 1 - i;
				}
			}
		}
	}

    //第一种情况
	for(int i = 0; i < len - 1; ++i)
	{
		gs[len - 1 - suffix[i]] = len - 1 - i;
	}

	return gs;
}

int bm_search(char* text, int text_len, char* pattern, int pattern_len)
{
	int* bc = CreateBC(pattern, pattern_len);
	int* gs = CreateGS(pattern, pattern_len);

	for(int i = 0; i <= text_len - pattern_len; )
	{
		int j = pattern_len - 1;
		for(; j >= 0 && pattern[j] == text[i+j]; --j);

		if(j < 0)
		{
			return i;
		}

		int bad_char_index = j;
		char bad_char = text[i + j];

		int bc_move = bad_char_index - bc[bad_char];
		if(bc_move < 0)
		{
			bc_move = 1;
		}

		int gs_move = gs[bad_char_index];

		int move = (bc_move > gs_move ? bc_move : gs_move);

		i += move;
	}

	if(bc != NULL)
	{
		delete bc;
		bc = NULL;
	}

	if(gs != NULL)
	{
		delete bc;
		gs = NULL;
	}
	return -1;
}

 

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值