字符串匹配——KMP算法

简介: 关于KMP算法的分析,我觉得这两篇博客写的不错: http://www.ruanyifeng.com/blog/2013/05/Knuth–Morris–Pratt_algorithm.html http://blog.csdn.net/v_JULY_v/article/details/6545192 下面的笔记也是参考了这两篇博客的。

关于KMP算法的分析,我觉得这两篇博客写的不错:

http://www.ruanyifeng.com/blog/2013/05/Knuth–Morris–Pratt_algorithm.html

http://blog.csdn.net/v_JULY_v/article/details/6545192

下面的笔记也是参考了这两篇博客的。


KMP算法是最有名的字符串匹配算法了。它是BF算法的改进版,至于是如何改进的,先引用上述第二篇博客里的一段话:


        “在继续分析之前,咱们来思考这样一个问题:为什么快排或者堆排序比直接的选择排序快?直接的选择排序,每次都是重复地比较数值的大小,每扫描一次,只得出一个最大(小值),再没有其它的成果。而快排,每扫一次,将数据分成了两部分,右边的数据都大于左边的数据,所以在下一次比较的时候,这两部分元素之间就不用比较了。再看堆排序,建堆的过程也是O(n)的比较,但比较的结果得到了最大(小)堆这种三角关系,之后的比较就不用再每一个都需要比较了。

        由上述思考,咱们总结出了一点优化的归律:采用一种简单的数据结构或者方式,将每次重复性的工作得到的信息记录得尽量多,方便下一次做同样的工作,这样将带来一定的优化。”


总结上面这段话的核心思想,就是把循环中要重复做的工作提取到循环外完成,从而提高效率。


下面用一个例子演示KMP匹配的过程。其中涉及的三个数据如下:

        source:源串,即要匹配的母船

        pattern:模式串,即子串

        next数组:其每个元素对应pattern的每个字符,表示当该字符pattern[j]不匹配source[i]时,应该从pattern的哪个下标开始重新匹配当前的source[i],具体求法后面介绍


本例中source为ABCABCABDE,pattern为ABCABD,第一次匹配,前5个字符均匹配成功,匹配第6个字符时如下:



此时发现source[5]和pattern[5]不匹配,BF算法的做法是从source[1]开始从新匹配pattern,即像下面这样:



而KMP算法则不会再去重新比较source[1...4]了,它会利用next的信息调整pattern。因为next[5]=2,所以下一次匹配将当前的source[5]和pattern[2]比较,即像下面这样:



这时发现source[5]和pattern[2]匹配,继续往下比较,直至pattern中所以元素都比较完了,如下:



此时已经在母串中找到了子串,i=9,m=6,所以返回的下标为i-m=3。


这样整个匹配过程就完了,感觉不过瘾吗,前面两篇推荐的博客里有更长的匹配串分析。


把上面的过程翻译成代码,也就是KMP算法的实现,如下:

int KMP(char *source, char *pattern)
{
	int i, j, m, n;
	int *next;

	i = 0;
	j = 0;
	m = strlen(pattern);
	n = strlen(source);
	next = (int *)malloc(m * sizeof(int));

	Next(pattern, next);<span style="white-space:pre">	</span>// 这是求next数组的函数,后面解释

	while (i < n && j < m)
	{
		if (j == -1 || source[i] == pattern[j])
		{
			++i;
			++j;
		}
		else
		{
			j = next[j];
		}
	}

	free(next);

	if (j == m)
	{
		return i - m;
	}
	else
	{
		return -1;
	}
}

下面来看next数组是如何求得的。


如前所述,next数组里保存的就是pattern[j]与母串中的字符不匹配时,该用哪个下标继续和这个字符匹配。若next[j]=k,则有:pattern[0...k-1] = pattern[j-k...j-1],而且这个k是最大的,即不会有pattern[0...k] =pattern[j-k-1...j-1]。


反过来想,如果已经知道了pattern[0...k-1] = pattern[j-k...j-1],next[j]是多少呢?要分两种情况考虑:

        1. pattern[k] != pattern[j],则next[j] = k

        2. pattern[k] = pattern[j],则next[j] = next[k]

第二种情况是因为,若pattern[k] = pattern[j]时,也使next[j]=k;则在pattern[j]于母串不匹配的情况下,再拿pattern[next[j]]即pattern[k]去比较的话,肯定也是不匹配的。这是隐藏的重复工作。使next[j] = next[k]就能避免这种重复工作。


那么怎么又能保证pattern[j]不会等于pattern[next[k]]呢?不急,一步一步想:最开始有next[0]=-1,如果pattern[1]=pattern[0],那么由规则2,next[1]=next[0]=-1;再如果pattern[2]=pattern[1],那么next[2]=next[1]=-1。看到了吗,三个一样的字符,如果是next依次为前一个元素的情况,最终都会是最前面那个next值,这就保证了最大的减少重复工作。


好吧,上面的解释很挫,意会吧。其实就跟离散树一样,同一个集合的元素都有相同的根。


有了上面的规则后,求next数组就比较方便了。用动态规划的策略很好实现:

void Next(char *pattern, int *next)
{
	int i, j, n;

	i = 0;
	j = -1;
	next[0] = -1;
	n = strlen(pattern);

	while (i < n - 1)
	{
		if (j == -1 || pattern[i] == pattern[j])
		{
			++i;
			++j;

			if (pattern[i] != pattern[j])
			{
				next[i] = j;
			}
			else
			{
				next[i] = next[j];
			}
		}
		else
		{
			j = next[j];
		}
	}
}

是不是感觉和KMP的框架很像,其实就是一回事,都是子串匹配,都利用了next数组。

不同之处在于:KMP是source匹配pattern,Next是一直从pattern的头部开始匹配后面所有的序列;在Next里面,next数组还没有完全生成,是利用前面生成的next加上匹配结果生成下一个next,这就是动规的策略。


目录
相关文章
|
28天前
|
算法
【算法】滑动窗口——找到字符串中所有字母异位词
【算法】滑动窗口——找到字符串中所有字母异位词
|
14天前
|
算法 C++
A : DS串应用–KMP算法
这篇文章提供了KMP算法的C++实现,包括计算模式串的next数组和在主串中查找模式串位置的函数,用于演示KMP算法的基本应用。
|
26天前
|
算法 Java
掌握算法学习之字符串经典用法
文章总结了字符串在算法领域的经典用法,特别是通过双指针法来实现字符串的反转操作,并提供了LeetCode上相关题目的Java代码实现,强调了掌握这些技巧对于提升算法思维的重要性。
|
24天前
|
算法
KMP算法
KMP算法
10 0
|
2月前
|
自然语言处理 算法 搜索推荐
字符串相似度算法完全指南:编辑、令牌与序列三类算法的全面解析与深入分析
在自然语言处理领域,人们经常需要比较字符串,这些字符串可能是单词、句子、段落甚至是整个文档。如何快速判断两个单词或句子是否相似,或者相似度是好还是差。这类似于我们使用手机打错一个词,但手机会建议正确的词来修正它,那么这种如何判断字符串相似度呢?本文将详细介绍这个问题。
228 1
|
2月前
|
数据采集 算法 JavaScript
揭开JavaScript字符串搜索的秘密:indexOf、includes与KMP算法
JavaScript字符串搜索涵盖`indexOf`、`includes`及KMP算法。`indexOf`返回子字符串位置,`includes`检查是否包含子字符串。KMP是高效的搜索算法,尤其适合长模式匹配。示例展示了如何在数据采集(如网页爬虫)中使用这些方法,结合代理IP进行安全搜索。代码示例中,搜索百度新闻结果并检测是否含有特定字符串。学习这些技术能提升编程效率和性能。
揭开JavaScript字符串搜索的秘密:indexOf、includes与KMP算法
|
1月前
|
算法 C++
惊爆!KPM算法背后的秘密武器:一行代码揭秘字符串最小周期的终极奥义,让你秒变编程界周期大师!
【8月更文挑战第4天】字符串最小周期问题旨在找出字符串中最短重复子串的长度。KPM(实为KMP,Knuth-Morris-Pratt)算法,虽主要用于字符串匹配,但其生成的前缀函数(next数组)也可用于求解最小周期。核心思想是构建LPS数组,记录模式串中每个位置的最长相等前后缀长度。对于长度为n的字符串S,其最小周期T可通过公式ans = n - LPS[n-1]求得。通过分析周期字符串的特性,可证明该方法的有效性。提供的C++示例代码展示了如何计算给定字符串的最小周期,体现了KPM算法在解决此类问题上的高效性。
44 0
|
2月前
|
算法 Java
KMP算法详解及其在字符串匹配中的应用
KMP算法详解及其在字符串匹配中的应用
|
6天前
|
算法 BI Serverless
基于鱼群算法的散热片形状优化matlab仿真
本研究利用浴盆曲线模拟空隙外形,并通过鱼群算法(FSA)优化浴盆曲线参数,以获得最佳孔隙度值及对应的R值。FSA通过模拟鱼群的聚群、避障和觅食行为,实现高效全局搜索。具体步骤包括初始化鱼群、计算适应度值、更新位置及判断终止条件。最终确定散热片的最佳形状参数。仿真结果显示该方法能显著提高优化效率。相关代码使用MATLAB 2022a实现。
|
6天前
|
算法 数据可视化
基于SSA奇异谱分析算法的时间序列趋势线提取matlab仿真
奇异谱分析(SSA)是一种基于奇异值分解(SVD)和轨迹矩阵的非线性、非参数时间序列分析方法,适用于提取趋势、周期性和噪声成分。本项目使用MATLAB 2022a版本实现从强干扰序列中提取趋势线,并通过可视化展示了原时间序列与提取的趋势分量。代码实现了滑动窗口下的奇异值分解和分组重构,适用于非线性和非平稳时间序列分析。此方法在气候变化、金融市场和生物医学信号处理等领域有广泛应用。