算法实现--KMP算法-匹配字符串

/*
有一个文本串S,和一个模式串P,现在要查找P在S中的位置,怎么查找呢?
*/

/**
* @brief 暴力匹配
* @param[in] s 主串
* @param[in] p 模式串
* @note 如果用暴力匹配的思路,并假设现在文本串S匹配到 i 位置,模式串P匹配到 j 位置,则有:
如果当前字符匹配成功(即S[i] == P[j]),则i++,j++,继续匹配下一个字符;
如果失配(即S[i]! = P[j]),令i = i - (j - 1),j = 0。相当于每次匹配失败时,i 回溯,j 被置为0。
* 时间复杂度为:O(n*m),n、m分别为主串和模式串的长度
*/
int ViolentMatch(std::string s, std::string p)
{
	int i = 0;
	int j = 0;

	//i是s的索引,j是p的索引
	while (i < s.size() && j < p.size())
	{
		if (s[i] == p[j]) //如果当前字符匹配成功(即S[i] == P[j]),则i++,j++    
		{
			i++; 
			j++;
		}
		else //如果失配(即S[i]! = P[j]),令i = i - (j - 1),j = 0    
		{
			i = i - j + 1;
			j = 0;
		}
	}

	return ((j == p.size()) ? (i -j) : -1); //匹配成功,返回模式串p在文本串s中的位置,否则返回-1
}

/**
* @brief KPM算法-获取模式串的预处理数组
* @param[in] p 模式串
* @param[out] next 预处理数组
* @note
* 时间复杂度为:O(m),m分别为模式串的长度
*/
// KPM算法的预处理数组是减少模式串与主串的匹配次数以达到快速匹配的目的的关键
	// next数组记录的是匹配失败位置前的模式串部分的最长前缀与最长后缀的长度,也就是在该位置匹配失败时,模式串位置索引应该跳转到该长度位置进行继续匹配
	// 当串与模式串匹配失败时,可以保持主串位置索引不进行回溯,使模式串位置跳转到这个长度值标识的位置
	// 如:在aabaabaafa中匹配aabaaf,当第一次匹配失败时,主串的位置是索引为5的'b',模式串的位置是索引为5的'f',
	// 在模式串中'f'前的字符串为aabaa,该部分字符串的最长前缀与最长后缀为"aa",长度为2,也标识着模式串中的位置索引2的'b'
	// 那么,第一次匹配失败后,可以保持主串的位置不变,将模式串的位置跳转到索引2的'b',继续进行后续比较,
	// 这样就达到了利用匹配失败部分信息来减少模式串与主串的匹配次数的目的
	std::vector<int> GetNext(std::string p)
	{
		if (p.empty())
			return{};

		// 记录的是匹配失败位置前的模式串部分的最长前缀与最长后缀的长度,也就是在该位置匹配失败时,模式串位置索引应该跳转到该长度位置进行继续匹配
		std::vector<int> next(p.size(), 0);

		next[0] = -1; // 模式串第一个字符前面没有字符,所以第一个字符前面部分最长前缀与最长后缀的长度记为-1
		int i = -1;  // i标识前缀的位置
		int j = 0;	// j标识后缀的位置

		// 只需要匹配到到p.size()-2位置,就能得到p.size()-1位置的值
		int len = p.size() - 1;
		// 确定好每一个位置前面部分最长前缀与最长后缀的长度值,也就是在该位置匹配失败时,模式串位置索引应该跳转到该长度位置进行继续处理
		while (j < len) 
		{
			// i标识前缀的位置,j标识后缀的位置,继续匹配相等的情况下,更新j后面一个位置的值,也就是j+1位置前面部分最长前缀与最长后缀的长度值
			if (i == -1 || p[i] == p[j])
				next[++j] = ++i;
			else // 如果匹配失败,使用之前存储的信息,递归前缀索引i,有可能一直递归到i=next[0]=-1,这时,下次循环会将j+1,并记录j+1位置的值为0
				i = next[i];
		}
		return std::move(next);
	}
/**
* @brief KMP算法
* @param[in] s 主串
* @param[in] p 模式串
* @note 
* 时间复杂度为:O(n+m),n、m分别为主串和模式串的长度
*/
int KMP(std::string s, std::string p)
{
	std::vector<int> next = GetNext(p); // 先获取模式串的预处理数组p

	int i = 0, j = 0;
	// 注意,若有符号数和无符号数比较,会先将有符号数转换为无符号数,可能出现问题
	while (i < static_cast<int>(s.size()) && j < static_cast<int>(p.size()))
	{
		if (j < 0 || s[i] == p[j]) // 当前字符匹配成功,或者j回溯到小于0的位置了,全部后移
		{
			++i;
			++j;
		}
		else // 当前匹配失败,保持主串ha的位置不变,更新跳转模式串ne的处理位置
			j = next[j]; // 这里可能会回溯到next[0]=-1;
	}

	return j == p.size() ? (i - j) : -1;  //匹配成功,返回模式串p在文本串s中的位置,否则返回-1
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
BF算法KMP算法都是模式匹配算法,但是它们的时间复杂度不同。BF算法的时间复杂度为O(m*n),其m和n分别为主模式的长度。而KMP算法的时间复杂度为O(m+n)。因此,当模式较长时,KMP算法的效率更高。 下面是BF算法KMP算法的介绍和演示: 1. BF算法(暴力匹配算法) BF算法是一种朴素的模式匹配算法,它的思想是从主的第一个字符开始,依次和模式的每个字符进行比较,如果匹配成功,则继续比较下一个字符,否则从主的下一个字符开始重新匹配。BF算法的时间复杂度为O(m*n)。 下面是BF算法的Python代码演示: ```python def BF(main_str, pattern_str): m = len(main_str) n = len(pattern_str) for i in range(m-n+1): j = 0 while j < n and main_str[i+j] == pattern_str[j]: j += 1 if j == n: return i return -1 # 测试 main_str = 'ababcabcacbab' pattern_str = 'abcac' print(BF(main_str, pattern_str)) # 输出:6 ``` 2. KMP算法(Knuth-Morris-Pratt算法KMP算法是一种改进的模式匹配算法,它的核心思想是利用已经匹配过的信息,尽量减少模式与主匹配次数。具体来说,KMP算法通过预处理模式,得到一个next数组,用于指导匹配过程的跳转。KMP算法的时间复杂度为O(m+n)。 下面是KMP算法的Python代码演示: ```python def KMP(main_str, pattern_str): m = len(main_str) n = len(pattern_str) next = getNext(pattern_str) i = 0 j = 0 while i < m and j < n: if j == -1 or main_str[i] == pattern_str[j]: i += 1 j += 1 else: j = next[j] if j == n: return i - j else: return -1 def getNext(pattern_str): n = len(pattern_str) next = [-1] * n i = 0 j = -1 while i < n-1: if j == -1 or pattern_str[i] == pattern_str[j]: i += 1 j += 1 next[i] = j else: j = next[j] return next # 测试 main_str = 'ababcabcacbab' pattern_str = 'abcac' print(KMP(main_str, pattern_str)) # 输出:6 ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值