字符串匹配经典算

       最近在刷数据结构,看到了字符串匹配算法KMP,BM,KP等,感觉是面试中应该要会的知识点,就先记录下来了,方便之后的复习查看:

      1.KMP算法

KMP算法是在暴力算法之上做了一些改进,不会重复的比对当前比对失败的前缀,即利用了匹配串本身的信息来构造一个查询表next,该表能够指导当次匹配失败下,如何对匹配串进行移动。可以省略掉一些重复比较的无用操作。

1.1next表构造

因为next表的构造其实本质也是字符串的匹配,故其整体的框架和KMP相类似:

 

int* buildnext(const char* p)
{
	size_t m = strlen(p), j = 0;
	int* N = new int[m];
	int t = N[0] = -1;
	while (j < m - 1)
	{
		if (t < 0 || p[j] == p[t])
		{
			j++; t++;
			N[j] = t;
		}
		else
			t = N[t]; //把t往小的方向拉,匹配更小的真前缀
	}
	return N;
}

1.2.KMP算法构造:

KMP的核心主要是1.1中的查询表,当次匹配失败时,能够让匹配串跳转到适当的位置,从新开始匹配:

int KMPMatch(const char* p, const char* T)
{
	int* next = buildnext(p);
	int n = strlen(T), i = 0;
	int m = strlen(p), j = 0;
	while (j < m && i < n)
		if (j < 0 || p[j] == T[i])
		{
			i++; j++;
		}
		else
			j = next[j];
	delete[] next;
	return i - j;
}

1.3 next表改进

int* buildnext(const char* p)
{
	size_t m = strlen(p), j = 0;
	int* N = new int[m];
	int t = N[0] = -1;
	while (j < m - 1)
	{
		if (t < 0 || p[j] == p[t])
		{
			j++; t++;
			N[j] = (p[j]!=p[t] ? t : N[t]);
		}
		else
			t = N[t]; //把t往小的方向拉,匹配更小的真前缀
	}
	return N;
}

 

2.BM算法

进一步的优化,采用坏字符和好后缀的策略。坏字符是利用字符匹配中的 “教训” 来进行优化,好后缀是利用字符匹配的先验知识 “经验”来进行优化。两者可以结合也可以分开实施优化。接下来分别的介绍坏字符BC和好后缀GS优化策略。

2.1坏字符表构造

坏字符就是匹配串p自右向左与T匹配时,出现不匹配T中的那个字符。根据该不匹配的教训,p要与之匹配的时候,必须在左侧找到这个坏字符,才能进行下一次的匹配:

int* buildBC(const char* p)
{
	int* bc = new int[256];  //和字符表等长
	for (size_t j = 0; j < 256; j++) bc[j] = -1; //先初始化,假设字符都未出现在p中
	for (size_t m = strlen(p), j = 0; j < m; j++)
		bc[p[j]] = j; // 记录出现字符在p中的最大Rank
	return bc;
}

2.2基于坏字符的匹配算法

既然确定了坏字符的下一次跳转,则可以推出下次匹配的相对位置关系:

int BMMatch(const char* p, const char* T)
{
	int* bc = buildBC(p);
	size_t i = 0;  //T的头指针
	while (strlen(T) >= i + strlen(p)) { //从T字符串开始搜索
		int j = strlen(p) - 1;
		while (p[j] == T[i + j]) //p与T自右向左的比对
			if (0 > --j) break;
		if (0 > j)
			break;
		else
			i += j - bc[T[i + j]]; //根据bc表来找到p中对应的坏字符,并以此进行移动
	}
	delete[] bc;
	return i;
}

 

你可能感兴趣的:(Leetcode)