天天看点

字符串匹配的BF算法与KMP算法

1.BF算法

//T为文本串如"Now is the time",P为模式串如"time",时间复杂度O(m*n),其中m与n为P和T的长度
int bf_match(string P, string T)
{
	int sizeP = P.size();
	int sizeT = T.size();
	int i = 0, j = 0;
	for (; i < sizeP - sizeT + 1; i++)
	{
		for (; j < sizeT; j++)
		{
			if (T[j] != P[i + j])
				break;
		}
		if (j >= sizeT)
			break;
	}
	return (i < sizeP - sizeT + 1) ? (i) : (-1);  //这里的i在成功时返回的是成功的位置,失败则是-1
}
           

2.KMP算法

  • (1)KMP算法的核心,是一个被称为部分匹配表(Partial Match Table)的数组,PMT中的值是字符串的前缀集合与后缀集合的交集中最长元素的长度。例如,对于”aba”,它的前缀集合为{”a”, ”ab”},后缀 集合为{”ba”, ”a”}。两个集合的交集为{”a”},那么长度最长的元素就是字符串”a”了,长 度为1,所以对于”aba”而言,它在PMT表中对应的值就是1。再比如,对于字符串”ababa”,它的前缀集合为{”a”, ”ab”, ”aba”, ”abab”},它的后缀集合为{”baba”, ”aba”, ”ba”, ”a”}, 两个集合的交集为{”a”, ”aba”},其中最长的元素为”aba”,长度为3。
  • (2)我们看到如果是在 j 位 失配,那么影响 j 指针回溯的位置的其实是第 j −1 位的 PMT 值,所以为了编程的方便, 我们不直接使用PMT数组,而是将PMT数组向后偏移一位。我们把新得到的这个数组称为next数组。
  • (3)现在,我们再看一下如何编程快速求得next数组。(next数组只与模式串字符串自身有关,与文本字符串无关,所以可以单独构建)其实,求next数组的过程完全可以看成模式字符串自匹配的过程,一旦字符串匹配成功,那么当前的next值就是匹配成功的字符串的长度。具体来说,就是从模式字符串的第一位(注意,不包括第0位,第0位必为-1,模式串的第一个字符就是第一位)开始对自身进行最长前后缀集的运算。 在任一位置,能匹配的最长长度就是当前位置的next值。(next数组的next[0] = -1,next[1] = 0,雷打不动,从next[2]开始真正计算,此时可以理解为模式串的j为0,而文本串的i为1,其中i与j为0时均为首)
  • (4)总结KMP算法思想,首先根据模式串自身特性,建立Next数组,其中存储在第多少位失配时,模式串自身回到多少位的信息。然后在主算法中,进行文本串i的按位匹配,每当出现失配时,i位置不变,而j根据Next数组回到一个相对较优的位置继续判断。Next[0] = -1实现的基础为j=-1.其核心在于使每次j = 0时都配对不上而回到Next[0]时能顺利进行而不是陷入死循环
int *buildNext(string P)
{
	int sizeP = P.size();
	int *Next = new int[sizeP + 1]; //注意n+1,使代码能够存到Next[sizeP]
	int i = 0; int j = -1;   //双指针,i代表文本串指针,j代表模式串指针(这里的文本串和模式串都是P自身)
	Next[0] = -1;
	while (i < sizeP)
	{
		if (j == -1 || P[i] == P[j])
		{
			i++;
			j++;
			//Next[i] = j;
			Next[i] = ((P[i] != P[j]) ? (j) : (Next[j]));   //优化的Next建表判断,吸取前面的当P[i] == P[j]时的经验
		}
		else
			j = Next[j];    //Next用于存储当失配时,模式串自身将回到的位置,所以这里可以在失配时调用自己回到对应的位置j
	}
	return Next;
}

int KMP_match(string P, string T)
{
	if (T.empty())
		return 0;

	int sizeP = P.size();
	int sizeT = T.size();

	if (sizeP > sizeT)
		return -1;

	int *next = buildNext(P);
	int i = 0, j = 0;    //i为文本串的指针,而j为模式串的指针
	while (i < sizeT && j < sizeP)
	{
		if (j == -1 || T[i] == P[j])   //注意next[0] = -1,所以j可能被移到-1位
		{
			i++;
			j++;
		}
		else
			j = next[j];   //模式串右移,注意文本串不会回退,next表用于存储当出现失配时j应该回退的地方
	}
	delete[] next;
	return (j == sizeP) ? (i - j) : (-1);;
}