串的BF算法和KMP算法个人总结

子串(模式串)的定位操作通常称作串的模式匹配

其中包含最初始的BF算法(Brute-Force)即简单匹配算法或者称作朴素的模式匹配算法,利用穷举法的思路

另一种就是改进后的KMP算法,还有对KMP算法的一种优化算法

现在先展示第一种BF算法的代码及解决思路:

#define _CRT_SECURE_NO_WARNINGS 1
#define MAXSIZE 255
#define ERROR 0
#include
#include

typedef struct {
	char ch[MAXSIZE];
	int length;
}SString;

int main()
{
	int compare(SString S, SString T, int pos);
	SString	S;
	SString T;//定义主串和模式串(子串),分别为S,T
	int i = 1;//i表示主串第一个元素的数组下标
	//比较串通常从数组下标为1开始,0位置不存储或者通常存放数组的长度
	//这里为了方便数组S.ch[0]和T.ch[0]不存放任何数据,长度事先定好并存在S.length和T.length中
	int j = 1;//j表示子串第一个元素的数组下标
	printf("请输入主串长度\n");
	scanf("%d%*c", &S.length);//使用%*c吃掉回车,不然scanf读取回车存在缓存区
	//下一次使用scanf输入char类型的元素会先读取缓存区的换行符(\n),影响多次scanf使用
	//这里有好几种解决方法,具体请参考《解决scanf读取回车问题》
	printf("请输入主串元素\n");
	while (i < S.length+1)//这里数组长度的定义是你输入的总元素的个数,不是所谓的总长度
		//最开始我们的0下标位置没有存放,所以i从1开始,循环S.length次,所以S.length)//记住比较最小从1开始,最大不能超过主串总长
		//因为我们把元素从1开始存放,所以从哪个元素开始位序和下标一样,不用担心不符合问题
	{
		exit(ERROR);
	}
	else
	{
		s = compare(S, T, pos);
	}
	if (s != 0)
		printf("字串从第%d位开始匹配成功",s);
	else
		printf("未匹配成功");

	return 0;
}

int compare(SString S, SString T, int pos)//BF算法,i每次要回溯
{
	int i = pos;//i代表起时从哪个位置开始比较
	int j = 1;//j代子串的元素位置
	while (i <= S.length&& j <= T.length)//如果i,j分别小于等于各自长度就可以继续比,大于就没得比了~
	{
		if (S.ch[i] == T.ch[j])//如果相等
		{
			++i;
			++j;//i和j都++,都准备开始比较各自的下一个元素
		}
		else
		{
			i = i - j + 2;//i回溯到当前开始比较位置的下一位置
			//这里理解为(i-j+1)+1,前面括号里面是刚开始比较的i的位置序号,后面加一表示往后移动一位
			//从后面一位开始,其位置元素作为新的比较起点开始比较
			j = 1;//j回溯到起始位置,每次匹配失败后子串就从头开始比较
		}
	}
	if (j > T.length) //最后如果比较成功了,子串对应的j达到了最后一位,即T.length
		//但是要记住,你最后一个元素匹配成功进入的if语句还有一个++i和++j,所以最后j=T.length+1
		return (i - T.length);//上面说明了,i最后还有个++,最后i值为比完的最后一位的下一位对应的i
	else
		return 0;
}

每次比较后,i都要回溯,j也得从第一位开始重新比较,效率其实非常低,于是一种新的算法腾空出现,就是最初的KMP算法。

他的核心思想就是i不回溯,一遍到底,j回溯到合适位置,不是每次从头开始

KMP的主函数和BF的差不多,先展示其匹配函数:

int KMP_cmp(SString S, SString T, int pos)//KMP算法
{
	int i = pos;
	int j = 1;
	int next[MAXSIZE];//定义next数组
	nextval(T, next);//为next数组赋值
	while (i <= S.length && j <= T.length)
	{
		if (j==0||S.ch[i] == T.ch[j])//多增加了j==0的判断
		{
			++i;
			++j;
		}
		else
		{
			j = next[j];//i不回溯,j回溯到适当位置
		}
	}
	if (j > T.length)
		return (i - T.length);
	else
		return 0;
}

再就是创建不完美的next数组的函数:

​
void nextval(SString T, int* next)//最原始的KMP算法的next数组
//如果碰到aaaaab这种子串,会被赋值012345,增加了不必要的回溯
{
	int i, k;
	i = 1;
	k = 0;
	next[1] = 0;
	while (i < T.length)
	{
		if (k == 0 || T.ch[i] == T.ch[k])
		{
			++i;
			++k;
			next[i] = k;
			
		}
		else
			k = next[k];
	}
}

​

先给大家简单分析一下k和i的含义和关系(改进后的照样继续分析即可):

首先是next数组生成时候我们把if和else抽出来分析:

串的BF算法和KMP算法个人总结_第1张图片

串的BF算法和KMP算法个人总结_第2张图片

串的BF算法和KMP算法个人总结_第3张图片

现在把最完美的KMP算法展示给大家:

(解决了前面重复元素的多次k回溯问题)

#define _CRT_SECURE_NO_WARNINGS 1
#define MAXSIZE 255
#define ERROR 0
#include
#include

typedef struct {
	char ch[MAXSIZE];
	int length;
}SString;

int main()
{

	int KMP_cmp(SString S, SString T, int pos);
	SString	S;
	SString T;//定义主串和模式串(子串)
	int i = 1;
	int j = 1;
	
	printf("请输入主串长度\n");
	scanf("%d%*c", &S.length);//使用%*c吃掉回车,不然scanf读取回车存在缓存区,影响多次scanf使用
	printf("请输入主串元素\n");
	while (i < S.length + 1)
	{
		scanf("%c", &S.ch[i]);
		i++;
	}
	printf("请输入子串长度\n");//这里就不用考虑回车的问题了,%c情况下才会读取,%d无影响
	scanf("%d%*c", &T.length);//使用%*c吃掉回车,不然scanf读取回车存在缓存区,影响多次scanf使用
	printf("请输入子串元素\n");
	while (j < T.length + 1)
	{
		scanf("%c", &T.ch[j]);
		j++;
	}
	int pos = 1;
	int s = 0;
	printf("请输入比较起始点:\n");
	scanf("%d", &pos);
	if (pos<1 || pos>S.length)//记住比较最小从1开始,最大不能超过主串总长
		//因为我们把元素从1开始存放,所以从哪个元素开始位序和下标一样,不用担心不符合问题
	{
		exit(ERROR);
	}
	else
	{
		s = KMP_cmp(S, T, pos);
	}
	if (s != 0)
		printf("字串从第%d位开始匹配成功", s);
	else
		printf("未匹配成功");

	return 0;
}

//主函数和BF算法一致,关键在于一个next数组的构建,他存放着j要回溯到的位置

void nextval(SString T, int *next)
{
	int i, k;
	i = 1;//i表示元素的位序
	k = 0;//k代表当前i位置元素的最大公共前缀+1
	//例如abcabd,next值为011123
	next[1] = 0;//第一个元素的next值人为先定义为0
	while (i < T.length)
	{
		if (k == 0 || T.ch[i] == T.ch[k])//如果k==0或者i位置和k位置的元素值相等
		{
			++i;
			++k;
			if (T.ch[i] != T.ch[k])//如果i位置的值和k位置的值不等,也就是外面if中以k==0条件进来的
				next[i] = k;//把此时k的值赋给i位置元素的next
			else
				next[i] = next[k];//如果相等i位置的next和k位置的next相等
		}
		else
			k = next[k];//如果既不相等k又不等于0,k就被赋值为k位置的next
	}
}

int KMP_cmp(SString S, SString T, int pos)//KMP算法
{
	int i = pos;
	int j = 1;
	int next[MAXSIZE];//定义next数组
	nextval(T, next);//为next数组赋值
	while (i <= S.length && j <= T.length)
	{
		if (j==0||S.ch[i] == T.ch[j])//多增加了j==0的判断
		{
			++i;
			++j;
		}
		else
		{
			j = next[j];//i不回溯,j回溯到适当位置
		}
	}
	if (j > T.length)
		return (i - T.length);
	else
		return 0;
}

下面是输入输出结果:

串的BF算法和KMP算法个人总结_第4张图片

 下面展示用数组第一位(下标为0)存储数组长度的相同算法(BF与KMP):

#define MAXSIZE 255
#define ERROR 0
#include
#include

typedef struct {
	char ch[MAXSIZE];
}SString;

int main()
{
	//int compare(SString S, SString T, int pos);
	int KMP_cmp(SString S, SString T, int pos);
	SString	S;
	SString T;
	int i = 1;
	int j = 1;
	char x, y;//先把串元素的值赋给x,y再把其赋给数组对应下标位置,这样能方便终止输入
	printf("请输入主串元素(输入'#'停止,每输入一个元素用enter换行)\n");
	scanf("%c%*c", &x);
	for (i = 1; x != '#'; i++)
	{
		S.ch[i] = x;
		scanf("%c%*c", &x);
	}
	S.ch[0] = i - 1;//数组下标为0的位置存放数组的长度
	fflush(stdin);//清空缓存区的所有字符,这里因为最后输入#后面接了一个换行,不清除后面子串第一个接收的就是换行符
	printf("请输入子串元素(输入'#'停止,每输入一个元素用enter换行)\n");
	scanf("%c%*c", &y);
	for (j = 1; y != '#'; j++)
	{
		T.ch[j] = y;
		scanf("%c%*c", &y);
	}
	T.ch[0] = j - 1;
	int pos = 1;//pos是比较起点,也就是从主串哪一位置开始和子串比较
	int s = 0;//s保存比较函数的返回值
	printf("请输入比较起始点:\n");
	scanf("%d", &pos);
	if (pos<1 || pos>S.ch[0])//记住比较最小从1开始,最大不能超过主串总长
		//因为我们把元素从1开始存放,所以从哪个元素开始位序和下标一样,不用担心不符合问题
	{
		exit(ERROR);
	}
	else
	{
		//s = compare(S, T, pos);
		s = KMP_cmp(S, T, pos);
	}
	if (s != 0)
		printf("字串从第%d位开始匹配成功", s);
	else
		printf("未匹配成功");

	return 0;
}

//int compare(SString S, SString T, int pos)//BF算法,i每次要回溯
//{
//	int i = pos;//i代表起时从哪个位置开始比较
//	int j = 1;//j代子串的元素位置
//	while (i <= S.ch[0] && j <= T.ch[0])//如果i,j分别小于等于各自长度就可以继续比,大于就没得比了~
//	{
//		if (S.ch[i] == T.ch[j])//如果相等
//		{
//			++i;
//			++j;//i和j都++,都准备开始比较各自的下一个元素
//		}
//		else
//		{
//			i = i - j + 2;//i回溯到当前开始比较位置的下一位置
//			//这里理解为(i-j+1)+1,前面括号里面是刚开始比较的i的位置序号,后面加一表示往后移动一位
//			//从后面一位开始,其位置元素作为新的比较起点开始比较
//			j = 1;//j回溯到起始位置,每次匹配失败后子串就从头开始比较
//		}
//	}
//	if (j > T.ch[0]) //最后如果比较成功了,子串对应的j达到了最后一位,即T.length
//		//但是要记住,你最后一个元素匹配成功进入的if语句还有一个++i和++j,所以最后j=T.length+1
//		return (i - T.ch[0]);//上面说明了,i最后还有个++,最后i值为比完的最后一位的下一位对应的i
//	else
//		return 0;
//}
// 
void nextval(SString T, int *next)
{
	int i, k;
	i = 1;//i表示元素的位序
	k = 0;//k代表当前i位置元素的最大公共前缀+1
	//例如abcabd,next值为011123
	next[1] = 0;//第一个元素的next值人为先定义为0
	while (i < T.ch[0])
	{
		if (k == 0 || T.ch[i] == T.ch[k])//如果k==0或者i位置和k位置的元素值相等
		{
			++i;
			++k;
			if (T.ch[i] != T.ch[k])//如果i位置的值和k位置的值不等,也就是外面if中以k==0条件进来的
				next[i] = k;//把此时k的值赋给i位置元素的next
			else
				next[i] = next[k];//如果相等i位置的next和k位置的next相等
		}
		else
			k = next[k];//如果既不相等k又不等于0,k就被赋值为k位置的next
	}
}

int KMP_cmp(SString S, SString T, int pos)//KMP算法
{
	int i = pos;
	int j = 1;
	int next[MAXSIZE];//定义next数组
	nextval(T, next);//为next数组赋值
	while (i <= S.ch[0] && j <= T.ch[0])
	{
		if (j==0||S.ch[i] == T.ch[j])//多增加了j==0的判断
		{
			++i;
			++j;
		}
		else
		{
			j = next[j];//i不回溯,j回溯到适当位置
		}
	}
	if (j > T.ch[0])
		return (i - T.ch[0]);
	else
		return 0;
}

下面是输入输出结果:

串的BF算法和KMP算法个人总结_第5张图片 

 

时间复杂度:(m,n分别是主串和子串的长度)

BF算法: O(m*n)

KMP算法 O(m+n)

KMP的思想和next数组创建建议多独立分析,先搞清思想再来用代码实现~~

独立研究next数组的创建过程------为什么需要、为什么这么建立

你可能感兴趣的:(数据结构个人总结,c语言,数据结构,算法)