字符串匹配算法代码(BF、KMP)

1. 暴力匹配 BF 算法 (C 语言实现)

/* Brute-Force 算法的实现(暴力匹配) */
/* 注意该算法基于自定义的 String 类型 */
#define MAXSIZE 40 /* 存储空间初始分配量 */

typedef char String[MAXSIZE + 1]; /*  0号单元存放串的长度 */

/* 返回子串 T 在主串 S 中第 pos 个字符之后的位置,若不存在则返回 0 */
/* 其中要求,T 非空,且 1≤pos≤StrLength(S) */
int index(String S, String T, int pos)
{
	int i = pos; // i 表示主串中当前匹配字符的下标,初始为 pos,表示从 pos 位置开始匹配
	int j = 1;	 // j 表示子串中当前匹配字符的下标,初始为 1
	/*
		算法终止的条件
		1. 匹配成功 => j 遍历到子串的最后一个字符且成功匹配(T[j] == S[i]),出循环时有 j == T[0] + 1
		2. 匹配失败 => i 遍历到最后一个字符开启最后一个大循环,此时 j 开始的小循环遍历也没有全部成功,
			故而遍历失败,出循环时有 j <= T[0] and i == S[0]
	*/
	while (i <= S[0] && j <= T[0])
	{
		if (S[i] == T[j]) // 字符匹配相等则继续匹配
		{
			i++;
			j++;
		}
		else // 字符匹配不相等,指针后退重新匹配
		{
			i = i - j + 2; // i 指针后退到上次匹配首位的下一位
			j = 1;		   // j 退回到子串首位
		}
	}
	if (j > T[0]) // 匹配成功,返回子串在主串 S 第 pos 个字符之后出现的位置
		return i - T[0];
	else
		return 0;
}

2. KMP 算法(Python 实现)

def build_next(patt):
	''' 根据传入的模板串 patt 生成 next 数组 '''
	''' 注意 patt[0] 代表第一个字符 '''
	next = [0] # next 数组初始化
	prefix_len = 0 # 当前位置对应子串中前后缀相似度
	i = 1 # i 是指向字符串的对应字符的指针
	
	''' 算法实现:逆推求解 next 数组对应的值 '''
	''' 
		算法实现思路 (以下角标可能存在错误,注重思路理解)
		if patt[j] == patt[next[j - 1]] 即,在上一个字符串前后缀基础上,再进一位的两个字符如果相同
			then next[j] = next[j - 1] + 1
		else
			then 在上一个字符串前后缀的基础上寻找更小的字符串
				if patt[j] == patt[next[next[j - 1]]]
					then next[j] = next[next[j - 1]] + 1
				else
					then 在上一个字符串前后缀的基础上寻找更小的字符串
						··· 到 0 为止
		注意:这里的 prefix_len 可以理解为相似前后缀的前缀的下一个字符的索引
		eg. 如下所示,patt[6] 对应的 next = 3,即 patt[2 + 1],即相似前缀的后一个字符的索引位置
			0 1 2 3 4 5 6 7
			A B A C A B A B
			0 0 1 0 1 2 3 2
	'''
	while i < len(patt):
		if patt[prefix_len] == patt[i]: # 在已知信息的基础上,相似度加一
			prefix_len += 1
			next.append(prefix_len)
			i += 1
		else:
			if prefix_len == 0:
				next.append(0)
				i += 1
			else:
				prefix_len = next[prefix_len - 1]
	
	return next

def kmp_search(str, patt):
	''' KMP 算法的实现 '''
	''' 
		算法实现思路 
		while 主串指针 i 在合法范围内
			if i,j 指向的字符相同
				then i,j 同时更新
			else
				if j=0 指向子串第一个字符
					then i 更新
				else
					then j 通过 next 数组进行更新,next 数组对应的值就是包括当前字符的字符串的前后缀最大相似度,也是 j 即将跳转的位置
	'''

	next = build_next(patt)

	i = 0 # 主串中的指针
	j = 0 # 子串中的指针

	while i < len(str):
		if str[i] == patt[j]:
			i += 1
			j += 1
		elif j == 0:
			i += 1
		else:
			j = next[j - 1] # 根据上一个匹配成功的字符对应的 next 值进行更新
		
		if j == len(patt): # j 对应的是 patt 的长度,就说明匹配成功(j = len - 1 代表 j 指向最后一个字符)
			return i - j
	
	return -1
		

3. KMP 算法 (C语言实现)

#define MAXSIZE 100				  /* 存储空间初始分配量 */
typedef char String[MAXSIZE + 1]; /*  0号单元存放串的长度 */

// 注意,第一个字符对应的索引时 1!

/* 通过计算返回子串T的next数组。 */
void get_next(String T, int *next)
{
	int i = 1; // 指向模板字符串字符的指针
	int k = 0; // 当前字符前的字符串对应的前后缀相似度
	next[1] = k;

	/*
		next 数组算法思路(注意,next 算法得到的值,是要更新的下标的位置)
		if j = 1
			then next[j] = 0,起到标记作用
		elif 1~j-1 组成的字符串最大前后缀相似度不为0
			then next[j] = 该最大前后缀相似度
		else
			then next[j] = 1 因为此时 1~j-1 对应的字符串全都不相同,因此要更新的话,也不过是需要将 j = 1

		上述代码已经处理了 j = 1 时 next = 0 的情形,因此我们只需要考虑上述条件分支结构的第二第三点
	*/

	while (i < T[0]) /* 此处T[0]表示串T的长度 */
	{
		if (k == 0 || T[i] == T[k])
		{
			++i;
			++k;
			next[i] = k;
		}
		else
			k = next[k]; /* 若字符不相同,则k值回溯 */
	}
}

/* 返回子串T在主串S中第pos个字符之后的位置。若不存在,则函数返回值为0。 */
/*  T非空,1≤pos≤StrLength(S)。 */
int Index_KMP(String S, String T, int pos)
{
	int i = pos;				   /* i用于主串S中当前位置下标值,若pos不为1,则从pos位置开始匹配 */
	int j = 1;					   /* j用于子串T中当前位置下标值 */
	int next[255];				   /* 定义一next数组 */
	get_next(T, next);			   /* 对串T作分析,得到next数组 */
	while (i <= S[0] && j <= T[0]) /* 若i小于S的长度并且j小于T的长度时,循环继续 */
	{
		if (j == 0 || S[i] == T[j]) /* 两字母相等则继续,与朴素算法增加了j=0判断 */
		{
			++i;
			++j;
		}
		else			 /* 指针后退重新开始匹配 */
			j = next[j]; /* j退回合适的位置,i值不变 */
	}
	if (j > T[0])
		return i - T[0];
	else
		return 0;
}

4. KMP 改进算法(C 语言实现)

#define MAXSIZE 100				  /* 存储空间初始分配量 */
typedef char String[MAXSIZE + 1]; /*  0号单元存放串的长度 */

// 注意,第一个字符对应的索引时 1!

/* 通过计算返回子串T的next数组。 */
void get_next(String T, int *next)
{
	int i = 1; // 指向模板字符串字符的指针
	int k = 0; // 当前字符前的字符串对应的前后缀相似度
	next[1] = k;

	/*
		next 数组算法思路(注意,next 算法得到的值,是要更新的下标的位置)
		if j = 1
			then next[j] = 0,起到标记作用
		elif 1~j-1 组成的字符串最大前后缀相似度不为0
			then next[j] = 该最大前后缀相似度
		else
			then next[j] = 1 因为此时 1~j-1 对应的字符串全都不相同,因此要更新的话,也不过是需要将 j = 1

		上述代码已经处理了 j = 1 时 next = 0 的情形,因此我们只需要考虑上述条件分支结构的第二第三点
	*/

	while (i < T[0]) /* 此处T[0]表示串T的长度 */
	{
		if (k == 0 || T[i] == T[k])
		{
			++i;
			++k;
			next[i] = k;
		}
		else
			k = next[k]; /* 若字符不相同,则k值回溯 */
	}
}

/* 返回子串T在主串S中第pos个字符之后的位置。若不存在,则函数返回值为0。 */
/*  T非空,1≤pos≤StrLength(S)。 */
int Index_KMP(String S, String T, int pos)
{
	int i = pos;				   /* i用于主串S中当前位置下标值,若pos不为1,则从pos位置开始匹配 */
	int j = 1;					   /* j用于子串T中当前位置下标值 */
	int next[255];				   /* 定义一next数组 */
	get_next(T, next);			   /* 对串T作分析,得到next数组 */
	while (i <= S[0] && j <= T[0]) /* 若i小于S的长度并且j小于T的长度时,循环继续 */
	{
		if (j == 0 || S[i] == T[j]) /* 两字母相等则继续,与朴素算法增加了j=0判断 */
		{
			++i;
			++j;
		}
		else			 /* 指针后退重新开始匹配 */
			j = next[j]; /* j退回合适的位置,i值不变 */
	}
	if (j > T[0])
		return i - T[0];
	else
		return 0;
}

说明

  • C 语言代码基于 《大话数据结构》 学习整理而成
  • Python 代码基于 哔哩哔哩 up 奇乐编程学院 相关视频学习整理而成

你可能感兴趣的:(算法,数据结构,c++)