首页 > 编程语言 >一文带你入木三分地理解字符串KMP算法(next指针解法)

一文带你入木三分地理解字符串KMP算法(next指针解法)

时间:2022-12-12 23:47:01浏览次数:78  
标签:匹配 key 后缀 KMP next 入木三分 公共 指针

1. KMP算法简介

温馨提示:在通篇阅读完并理解后再看简介效果更佳
以下简介由百度百科提供https://baike.baidu.com/item/KMP%E7%AE%97%E6%B3%95/10951804
KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt提出的,因此人们称它为克努特—莫里斯—普拉特操作(简称KMP算法)。KMP算法的核心是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是通过一个next()函数实现,函数本身包含了模式串的局部匹配信息。KMP算法的时间复杂度O(m+n)

2. 对算法本质的理解

注意:为了叙述方便,本小节中的索引都从1开始而非0

· 抽象理解人眼是如何匹配字符串的

我们要在字符串1中查找字符串2,则把字符串1称为文本串,字符串2成为匹配串。
人眼在文本串与匹配串中来回扫描,一个个判断两串字符是否相等(你可能觉得你能一下子比较五个以上字符,但不妨理解为你的大脑还是一个个比较的)。如下图所示:当人眼发现两个字符不相等时,视线(图中红色区块)不会移到两串的起始位置重新比较,而是会找到文本串视线曾经过区域中与匹配串某前缀(图中黄色区块)相等的地方开始比较,

当我们对匹配字符串时人视线的移动进行模拟便可以实现KMP这一高效的匹配算法。

· 用最大公共前后缀与指针模拟人眼操作

我们如此定义最大公共前后缀:在匹配串位置[1,N]的区块中找一个子串,使得该子串既是最长的前缀,又是最长的后缀,并且该子串不能等于该区块本身,则称该子串为匹配串位置[1,N]的区块的最大公共前后缀。
例如下图:
对于匹配串AAXAAXAA,可以发现AAXAA就是它的最大公共前后缀。

需要用到最大公共前后缀做什么呢?别急,咱们根据以下几个步骤循序渐进地理解:
1.假设匹配串与文本串在位置[1,W-1]都相等,在位置W字符不等,在此条件下我们设pre为[1,W-1]区域中匹配串的某前缀(下文会确定下来),设指针i指向文本串中的字符,指针j指向匹配串中的字符。
模拟人眼的操作,此时我们要在文本串[1,W-1]之间(去除已经和pre比较过的部分)寻找pre并由此移动指针。

2.由于是从前向后匹配字符串的,所以如果pre在[1,W-1]这个文本串区块间存在,其第一次出现一定是出现在区块的末尾,也就是说它就是区块的后缀。又由于匹配串与文本串在位置[1,W-1] 都相等,所以pre也是匹配串的后缀,于是成为了匹配串[1,W-1]区域的公共前后缀。
图例:

3.我们这时候就可以尝试着使用公共前后缀将比较字符串的视线移动用指针具象化。当双指针所指的字符相同时,令i++;j++即可;若字符不同时,我们如此考虑:

  • 当文本串[1,W-1] (去除已经和pre比较过的部分)中含有pre,即在[1,W-1] 有公共前后缀(pre长大于0)时。我们记len[W-1]为匹配串的前缀终止位置,从上文得文本串后缀的终止位置为W-1,由于匹配串的前缀与文本串的后缀相对应,所以我们只需要从匹配串前缀与文本串后缀之后开始比较即可。即令i不变仍为W;j=len[W-1]+1。
  • 我们又额外考虑当文本串[1,W-1] (去除已经和pre比较过的部分)中不含pre,即在[1,W-1] 无公共前后缀(pre长为0)时的情况。这时完全可以看作len[W-1]=0,与上一种情况一致,i不变保持W;j=len[W-1]+1。

指针移动图例:

所以无论哪种情况,文本串上的指针i不会回退,而匹配串的指针j则会根据不同情况而回退

4.到这公共前后缀的价值已经很明确了,只要找出每一个[1,W-1]区域匹配串的公共前后缀之长len[W-1],那么就可以得到如下指针移动公式,使得每次字符不同时,指针的移动模拟了人眼的匹配过程。


5.这里我们应当确立步骤1中的某前缀应当为满足匹配串[1,W-1]区域的公共前后缀最大时的前缀。也就是说要pre满足其为匹配串[1,W-1]区域的最大公共前后缀。理由:见下图两个取不同大小公共前后缀的示例的比较次数,其中橙色区块为公共前后缀,蓝色区块为指针j回退后还需要比较的字符,明显取大的公共前后缀的比较字符更少(因为大的公共前后缀中包括了小的公共前后缀情况下还需要比较的字符)。

以AA为公共前后缀时:还需比较7个字符

以AAXAA为公共前后缀时:还需比较4个字符

归纳:到这里为止,我们所有的问题就转化为了求len[W-1],即求匹配串[1,W-1]中最大公共前后缀的值。

3. 使用next数组求解最大公共前后缀长度

注意:上文说到,我们只要知道len[W-1]的值,便可以在位置W处字符不等式快速找到指针回退的位置。然而在大多数官方的解释中,这个len数组被命名为next,为了规范化,我们下文中会用next数组来称呼len数组。此外,索引仍从1开始。

我们设字符串F表示匹配串,设next[index]表示匹配串[1,index]区域中最大公共前后缀的长度。并使用使用双指针求解,指针i指向当前字符位置(也可以看作就是后缀终止位置),用指针j指向[1,i]间最大公共前后缀的前缀终止位置(同时可以发现j就是最大公共前后缀长度),
求最大公共前后缀的过程如下,当i从1向匹配串末尾遍历时

  • 若F[j+1]=F[i],说明当下前缀之后的第一个位置与后缀终止字符相等,那么最大公共前后缀就可以增加一个字符,前缀终止位置可以指向下一个字符,即next[i]=j+1;j++。
  • 若F[j+1]\(\neq\)F[i],此时的情况就需要分多步进行理解:

1.可以把当前的状态用下图表示,其中整个矩形为匹配串[1,i]的部分,可见A=F[j+1],B=F[i]。

2.现在我们先将问题转化为以下这种情况,找到一个如下的橙色部分,判断A是否与C相等,相等则橙色部位加上F[i]即为[1,i]的最大公共前后缀。

所以我们可以确定橙色部分长度为next[j]。
3.将上图简化为下图,我们发现这个状态是似曾相识的

我们不如直接令j回退到next[j]处,然后再判断F[j+1]与F[i]是否相等,这时一切又转化为整个过程的开始。
4.可以发现,当F[j+1]\(\neq\)F[i]时,我们总是周而复始找到j这个最大公共前后缀的最大公共前后缀,然后重新判断,直到无最大公共前后缀或者判断出相等。

归纳:至此我们已经可以对于任意索引index,求出匹配串[1,index]区间的最大公共前后缀长度,结合上部分指针移动公式即可完成KMP算法。

4. 用c++代码实现

#include<bits/stdc++.h>
using namespace std;
//索引仍然从1开始 
void getNext(int* next,string key){
	//输入空next数组与匹配串key,将next数组生成为key的最大公共前后缀数组
      int j=0;next[1]=0;
      for(int i=2;i<key.length();i++){
      	while(j>0 && key[i]!=key[j+1]) j=next[j];//不断寻找最大公共前后缀的最大公共前缀,直到最大公共前后缀或者判断出相等。
		if(key[i]==key[j+1]) j++;//判断出相等,最大公共前后缀增长
		next[i]=j;
	  }
}
int KMP(string text,string key){
	/*输入文本串与匹配串,返回匹配串在文本串中的位置
	找不到则返回-1*/ 
	text=" "+text;key=" "+key;//因为索引从1开始,所以要在0的位置垫上空格 
    if(key.length() == 0)return -1;
    int next[key.length()+1];
    getNext(next, key);//生成匹配串的next数组 
    int j=1,i=1;
    while(j < key.length() && i < text.length()){
    	if(text[i] == key[j])i++,j++;//当字符相等时的公式 
		else j = next[i-1]+1;//当字符不等时的公式 
	}
	if(j == key.length())return i-key.length()+1;
    return -1;
}
int main() {
	string text,key;//文本串与匹配串
	cin>>text>>key;
    int i=KMP(text,key);
    printf("匹配串出现在文本串第%d位",i);
    return 0;
}
输入:AAXAAXAAXAAD AAXAAXAAD
输出:匹配串出现在文本串第4位

写文不易,若对您有帮助请给予鼓励。

标签:匹配,key,后缀,KMP,next,入木三分,公共,指针
From: https://www.cnblogs.com/Yvchen/p/16961950.html

相关文章

  • 我和ABP vNext 的故事
    AbpVNext是Abp的.NETCore版本,但它不仅仅只是代码重写了。Abp团队在过去多年社区和商业版本的反馈上做了很多的改进。包括性能、底层的框架设计,它融合了更多优雅的设计实......
  • 扩展KMP算法
      前文已经介绍了经典的​​KMP算法​​​,本文继续介绍KMP算法的扩展,即扩展KMP算法。  问题定义:给定两个字符串S和T(长度分别为n和m),下标从0开始,定义extend[i]等于S[i]......
  • KMP算法(1):如何理解KMP
    系列文章目录KMP算法(1):如何理解KMP​​​KMP算法(2):其细微之处​​一:背景  给定一个主字符串(以S代替)和模式串(以P代替),要求找出P在S中出现的位置,即串的模式匹配问......
  • Link标签在Next.js框架下报错 use <Link legacyBehavior>?
    直接把a标签去掉,数据直接放Link标签就好了。  TRANSLATEwithxEnglishArabicHebrewPolishBulgarianHindiPortugueseCatalanHmongDawRo......
  • P5410 【模板】扩展 KMP(Z 函数)
    题目链接P5410【模板】扩展KMP(Z函数)【模板】扩展KMP(Z函数)题目描述给定两个字符串\(a,b\),你要求出两个数组:\(b\)的\(z\)函数数组\(z\),即\(b\)与\(b\)的......
  • 初始 ABP.vNext
    一、ABP.vNext了解与使用在这之前我们先了解一下什么是ABP?ABP是ASP.NETBoilerplates缩写,是一个开源并且有丰富文档的应用框架,开发宗旨是:“为所有公司,所有开发人员,开......
  • 芒果改进YOLOv5系列:全网首发最新原创打造RepGhostNeXt结构,基于重参数化结构,实现硬件高
    OverridetheentrypointofanimageIntroducedinGitLabandGitLabRunner9.4.Readmoreaboutthe extendedconfigurationoptions.Beforeexplainingtheav......
  • 一文搞懂next 、hasNext,nextLine、hasNextLine
    在Java中,HasNext和Next是Scanner类的两个方法,它们用于读取用户输入。HasNext方法会检查用户是否在当前行输入了一个值,如果有,它会返回true,否则返回false。Next方......
  • KMP算法详解-字符串匹配
    1.什么是KMP是由这三位学者发明的:Knuth,Morris和Pratt,所以取了三位学者名字的首字母。所以叫做KMP2.KMP的用处KMP主要用于字符串匹配。KMP的主要思想是当出现字符串不匹......
  • vue源码中的nextTick是怎样实现的
    一、Vue.nextTick内部逻辑在执行initGlobalAPI(Vue)初始化Vue全局API中,这么定义Vue.nextTick。functioninitGlobalAPI(Vue){//...Vue.nextTick=ne......