数据结构7-----串(KMP、next数组)

串:

串(String)是由零个或多个字符组成的有序序列,又叫字符串。

字符串的匹配:

串S和T存在,T时非空串,若主串中存在和串T值相同的字串,则返回它在主串s中第pos个字符之后第一次出现的位置,否则返回-1。

这种子串的定位操作通常称作串的模式匹配。一般把S称为主串,T称为模式串。

一般字符串的匹配算法主要有:

(1)朴素的字符串匹配算法(Naive String Matching Algorithm)

(2)Knuth-Morris-Pratt字符串匹配算法(KMP算法)

假设我们要从s="goodgoogle"寻找T="google"。

朴素的字符串匹配算法

(1)将主串中的第一个和子串中的第一个比较,如果匹配成功,则将主串和子串中的第二个比较。如果两个串中出现不相同的情况,就将主串的第二个和子串的第一个比较。这样一直到匹配完成或者,主串完结。

                                        (1) (2)                                                                      (3)(4)                                                                               (5)

思想比较简单,程序也比较好实现。

int simpleString(string mainStr, string modeStr, int pos)
{
	int mainStrLen = mainStr.length();
	int modeStrLen = modeStr.length();
	//记录主串中模式串的起始位置
	int p = -1;
	int i = pos, j = 0;
	while (pos > -1 && i < mainStrLen)
	{
		if (mainStr[i] == modeStr[j])
		{
			++j;
			++i;
		}
		else
		{
			i = i - j + 1;
			j = 0;
		}
		if (j == modeStrLen)
		{
			p = i - j;
			break;
		}
	}
	return p;
}

KMP算法

观察上面的朴素的字符串匹配算法我们就会发现,在第一次匹配失败后,其实在从主串的第二位置继续寻找是大可不必的,因为“google”中不相同的元素,所以我们可以在主串中下标为4的位置开始重新开始匹配,这样就可以减少比较的次数。这就是KMP的基本思路。那如何计算匹配失败后重新开始匹配的位置,就是KMP算法的关键(next数组)。

KMP的算法流程:

假设现在文本串S匹配到 i 位置,模式串P匹配到 j 位置

    (1)如果j = -1,或者当前字符匹配成功(即S[i] == P[j]),都令i++,j++,继续匹配下一个字符;
    (2) 如果j != -1,且当前字符匹配失败(即S[i] != P[j]),则令 i 不变,j = next[j]。

这是问题的关键就转化成如何求next数组(KMP的核心问题):

next数组中存储的是除当前字符外最长公共前缀后缀。那么什么是最长公共前缀后缀那?简单来说就是一段字符前面和后面都相同的部分的长度,例如:aba,前缀有(a,ab)后缀有(a,ba),那aba的最长公共前缀后缀就是1,同理abab的最长公共前缀后缀就是2。但next中存储的是除当前字符外最长公共前缀后缀,需要将第一步中求出的数组,整体后移一位,然后将第0位的元素置成-1。next数组虽然描述起来很简单,在给定一个特定的串下用笔计算也不难,但问题在是模式串是不确定的,所以算法的理解上还是有一定的困难的,尤其是匹配失败后的回溯过程。下面是求解next数组的代码:

/*
(1)先生成最长公共前缀后缀表,不做转化。
(2)转化为next数组。
*/
void makeNext(string modeStr, vector<int>& pNext)
{
	//只有一位时为0
	int modeStrLen = modeStr.length();
	//i记录当前位置
	//j记录当前位置之前的字符串最大公共前缀后缀。
	int i = 1, j = 0;
	//存储的是从0位置到当前下标位置之间字符串的最大公共前缀后缀
	pNext.push_back(0);
	while (i < modeStrLen)
	{
		//当前的modeStr[j]和modeStr[i]不匹配
		while (j>0 && modeStr[i] != modeStr[j])
		{
			j = pNext[j-1];
		}
		/*
		退出循环:
		(1)j==0,没有在前面寻找到和mode[i]相同的元素
		(2)modeStr[i]==modeStr[j],在前面寻找到了和mode[i]相同的元素。
		*/
		if (modeStr[i] == modeStr[j])
		{
			++j;
		}
		pNext.push_back(j);
		++i;
	}
	//转化为next数组
	pNext.insert(pNext.begin(), -1);
	pNext.pop_back();
}

next数组一旦求出,那么KMP问题也就完成了99%了,下面是KMP算法:

int KMP(string mainStr, string modeStr)
{
	int mainStrLen = mainStr.length();
	int modeStrLen = modeStr.length();
	vector<int> next;
	makeNext(modeStr, next);
	int i = 0, j = 0, pos = -1;
	while (i < mainStrLen)
	{
		if (j==-1||mainStr[i] == modeStr[j])
		{
			++i;
			++j;
			if (j == modeStrLen)
			{
				pos = i - j;
				break;
			}
		}
		else
		{
			j = next[j];
		}
	}
	return pos;
}

如果理解可以看看这篇文章:教你从头到尾彻底理解KMP算法

猜你喜欢

转载自blog.csdn.net/qq_39038983/article/details/86577293