KMP算法详解

1.前言

KMP算法又称为模式匹配算法,主要是来求一个长的字符串中是否存在相对应的子串。KMP算法可谓是数据结构串中最难的部分了,我也是花费好长时间翻看大量资料才终于理解,最后写下这篇博客,欢迎大家批评指正,

2.模式匹配的暴力做法

所谓暴力做法就是逐个比较文本串中的每个位置开始的子串与模式串是否匹配,如果不匹配,则直接回退到文本串回退到上一次开始比较的位置的下一个位置。

如下面文本串和模式串不匹配,那么下一步就会回退到开始比较的下一个位置,

即文本串b的位置然后再接着比较,最后一直暴力匹配到最后,此时的时间复杂度会很高。

下面是暴力匹配的代码,

void Violent_match(string &l,string &s)
{
	for(int i=0;i<l.size();i++)
	{
		bool flag=true;
		for(int j=0,k=i;j<s.size();j++,k++)
		{
			if(l[k]!=s[j])
			{
				flag=false;
				break;
			}
		}
		if(flag)
		cout<<i<<endl;
	}
}

3.KMP算法原理

看了上面的暴力做法,相信各位心中也会想到其实在匹配失败时,有时没有必要直接回溯到原来位置,而是可以回溯到模式串前面最大再可以与文本串匹配的地方。

如下面的例子,此时文本串和模式串不匹配,但是前面已经匹配了一部分,

我们接着利用前面已经匹配的信息,来进行第一步回溯,

此时仍不匹配,则在进行回溯,

然后接着匹配

最后匹配成功

那么我们如何知道当文本串和模式串不匹配时,模式串该回溯到哪个地方,我们这时候就需要用一个数组来存储每次不匹配时该回溯的地方,这就是大名鼎鼎的next数组,也是KMP算法的核心部分。

下面我们先给出KMP模式匹配算法的代码

void KMP(string &l,string &s) //下面ne[]即是next数组
{
	for(int i=0,k=-1;i<l.size();i++) //遍历文本串
	{
		
		while(k!=-1&&l[i]!=s[k+1])
		{
			//如果不匹配的话,就根据next数组回溯到上一个位置
			k=ne[k];
		}
		
		if(l[i]==s[k+1])
		{
			//如果匹配的话,就看下一个位置是否匹配
			k++;
		}
		
		if(k+1==s.size())
		{
			cout<<i-s.size()+2<<endl;//最后输出匹配的子串的位置,从下标1开始输出
			k=ne[k];//匹配后回溯,寻找下一个位置
		}
	}
}

简而言之,KMP算法核心思想在文本串中从左到右逐个比较字符。当发现不匹配时,根据前面已匹配的信息,也就是next数组调整模式串的位置,使模式串向右移动至下一个匹配位置再继续匹配。

4.求next数组

到此处,就是KMP算法最抽象,最难以理解的地方了,很多人就是前面都很明白是怎么回事,到这里就迷糊了,下面我先奉上代码然后再为大家一步一步解释。

void Next(string &s) //下面的ne[]即是next数组
{
	ne[0]=-1;//next数组第一个位置不能匹配时无法回溯,故肯定是-1
	
	for(int i=1,k=-1;i<s.size();i++)
	{
		while(k>-1&&s[k+1]!=s[i])
		{
			//如果不匹配的话,就进行回溯,看前面有没有可能再度匹配成功
			k=ne[k];
		}
		
		if(s[k+1]==s[i])
		{
			//如果匹配的话,还要接着看下一个位置是否匹配
			k++;
		}
		
		ne[i]=k; //记录回溯位置
	}
}

 下面我们就来看一个例子:对于模式串abababc,来求next数组。

对于第一个字母a,如果不匹配的话,不用说肯定是没有办法回溯的,故next[0]=-1。

对于第二个字母b,前面没有可以与它可以匹配的,只能回溯到队首,next[1]=-1。

对于第三个字母a,前面第一个字母a与其匹配,故k++,然后接着判断是否下一个字母也匹配,此时next[2]=0;

对于第四个字母b,前面第二个字母b与其匹配,故k++,next[3]=1。

对于第五、六个字母a,b,前面同样可以匹配,故next[4]=2,next[5]=3。

对于最后一个字母c,很明显不能匹配,故需回溯到next[5]=3的位置,这里是字母b仍然与字母c不匹配,此时再回溯到next[3]=1的位置,此时仍不匹配,故最后只能回溯到next[1]=-1,此时可以得到next[6]=-1。

到此我们就可以完整的求出next数组。

5.总结

最后奉上完整代码,供大家参考学习。

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;

const int N=1510;

int ne[N];

void Next(string &s) //下面的ne[]即是next数组
{
	ne[0]=-1;//next数组第一个位置不能匹配时无法回溯,故肯定是-1
	
	for(int i=1,k=-1;i<s.size();i++)
	{
		while(k>-1&&s[k+1]!=s[i])
		{
			//如果不匹配的话,就进行回溯,看前面有没有可能再度匹配成功
			k=ne[k];
		}
		
		if(s[k+1]==s[i])
		{
			//如果匹配的话,还要接着看下一个位置是否匹配
			k++;
		}
		
		ne[i]=k; //记录回溯位置
	}
}

void KMP(string &l,string &s)
{
	for(int i=0,k=-1;i<l.size();i++) //遍历文本串
	{
		
		while(k!=-1&&l[i]!=s[k+1])
		{
			//如果不匹配的话,就回溯到上一个位置
			k=ne[k];
		}
		
		if(l[i]==s[k+1])
		{
			//如果匹配的话,就看下一个位置是否匹配
			k++;
		}
		
		if(k+1==s.size())
		{
			cout<<i-s.size()+2<<endl;//最后输出匹配的子串的位置,从下标1开始
			k=ne[k];//匹配后回溯,寻找下一个位置
		}
	}
}

int main()
{
	string s1,s2;
	cin>>s1>>s2;
	
	Next(s2);
	
	KMP(s1,s2);
	
	for(int i=0;i<s2.size();i++)
	{
		cout<<ne[i]+1<<" "; //最后打印出next数组每一个值,下标从0开始
	}
	
}

上面代码题目来源为https://www.luogu.com.cn/problem/P3375,大家可以自行练习,有不懂的欢迎大家提问。

  • 6
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
KMP算法(Knuth-Morris-Pratt算法)是一种用于解决字符串匹配问题的高效算法。它的主要思想是利用匹配失败时的信息,尽量减少比较次数,提高匹配效率。 KMP算法的核心是构建一个部分匹配表(Partial Match Table),也称为Next数组。这个表记录了在匹配失败时应该将模式串向右移动的位置。 构建部分匹配表的过程如下: 1. 首先,将模式串中的第一个字符的Next值设为0,表示当匹配失败时,模式串不需要移动; 2. 然后,从模式串的第二个字符开始,依次计算Next值; 3. 当第i个字符与前面某个字符相同的时候,Next[i]的值为该字符之前(不包括该字符)的相同前缀和后缀的最大长度; 4. 如果不存在相同的前缀和后缀,则Next[i]的值为0。 有了部分匹配表之后,KMP算法的匹配过程如下: 1. 用i和j来分别表示模式串和主串的当前位置; 2. 如果模式串中的字符和主串中的字符相同,那么i和j都向右移动一位; 3. 如果模式串中的字符和主串中的字符不同,那么根据部分匹配表来确定模式串的下一个位置; 4. 假设当前模式串的位置为i,根据部分匹配表中的值Next[i],将模式串向右移动Next[i]个位置; 5. 重复上述步骤,直到找到匹配或者主串遍历完毕。 KMP算法的时间复杂度为O(m + n),其中m和n分别是模式串和主串的长度。相比于暴力匹配算法的时间复杂度为O(m * n),KMP算法能够大幅减少比较次数,提高匹配效率。 综上所述,KMP模式匹配算法通过构建部分匹配表并利用匹配失败时的信息,实现了高效的字符串匹配。在实际应用中,KMP算法被广泛地应用于文本编辑、数据搜索和字符串处理等领域。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值