算法笔记--字符串匹配KMP算法,简单易懂

KMP算法

KMP算法看懂了就会感觉挺容易的,思路也很清晰。看不懂就会稀里糊涂。

先说什么是字符串匹配

字符串匹配。简单来说,就是给你两个字符串,寻找其中一个字符串是否包含另一个字符串,如果包含,返回包含的起始位置。
比如下面两个字符串:

string s1="bacbababadababacacabc";
string s2="ababaca";

s1中包含s2,在s1下标10处;
bacbababadababacacabc

再说说KMP算法

看完字符串匹配,你可能想到的可能是拿s1的首位与s2的首位比较,相等就s1和s2都后移一位,不相等s1就后移一位s2返回到第一位。
当然想到这种算法思想是很好的,但时间复杂度你算一下,如果s1有n位,s2有m位,最倒霉的情况(就是查到最后才查到),循环就运行了(m-n+1)*n次,这样时间复杂度就是O(m * n)。
想必你也看出来了,时间复杂度之所以那么大,是因为它逐步查找太慢了,可不可以让它一下移动好几位,KMP算法就做到了这一点,这样KMP算法就可以实现时间复杂度为O(m+n)
KMP算法分为两大块,next数组构造KMP函数构造

1、next数组构造

next数组是针对s2,也就是子串构造的一个转移函数。其含义就是一个固定字符串的最长前缀和最长后缀相同的长度。(这一点一定要记清,即使代码不会,next函数也一定要明白怎么构建)。


普及一下最长相同前缀和后缀的含义。
最长前缀:是说以第一个字符开始,但是不包含最后一个字符。
最长后缀:以最后一个字符开始,但是不包含第一个字符。
比如  ababa 
前缀有a ab aba abab
后缀有baba aba ba a
最长前缀就是abab,最长后缀就是baba,最长相同前缀和后缀就是aba
next数组记录的就是最长相同前缀和后缀,这里就是3

言归正传,继续说next数组:
比如这里的s2,ababaca;长度是7,所以就有了next[0]next[1]next[2]next[3]next[4]next[5]next[6]

注意此版本的next [ i ] 这里是看前 i 位的最长相同前缀和后缀。版本不同,next数组也不同,不过是相同的逻辑。(比如史老师的课上讲的next数组就是在此版本的基础上+1)

  1. 首先是next[0],初始让next[0]=-1(第一个数特殊,后面讲KMP时会提到) 。
  2. next[1]就是求a的最长相同前缀和后缀,这里是" ",没错就是空,所以next[1]=0
  3. next[2]对应 ab,对应" ", next[2]=0
  4. next[3]对应 aba,对应"a",next[3]=1
  5. next[4]对应 abab,对应"ab",next[4]=2
  6. next[5]对应 ababa,对应"aba", next[5]=3
  7. next[6]对应 ababac,对应" ", next[6]=0

所以next数组这里就是next[0]=-1
next[1]=0next[2]=0next[3]=1next[4]=2next[5]=3next[6]=0

2、KMP函数的构造

KMP函数就是拿s2函数和next数组与s1进行比较,next数组就是让s1尽可能多地“滑动”。
再拿来这俩字符串

string s1="bacbababadababacacabc";
string s2="ababaca";

首先拿s1的第一位与s2的第一位进行比较,如果第一位都不相同,那s1直接后移一位,s2重新从第一位开始(因为是第一位都不相等,这里特殊标记了一下,这就是next [ 0 ] = -1d的原因)。
如果第一位相同,看第二位,第二位不相同,那s2就返回第(next [ 1 ] 的值)的位置再继续比较s1的第二位和s2的第(next [ 1 ] 的值)的位置,如果还不相等s2就继续往前移…最坏的情况是前移到s2的第一位,这样的话就是上一段所讲的next[0]的情况了
第一位,第二位,第三位…
直到s2比到最后一位,如果相等就返回当前s1位置减去s2长度加1,就是匹配字符串的初始位置。
那如果s1比到最后一位时s2还是没能匹配到最后一位,那就是没找到匹配字符串
(其实你可以看一下,s2前移的过程就是s1往后滑动的过程,s2前移几位,s1就后滑几位)

代码实现

全局变量

string s;
int nxt[10000];

getnxt()函数

void getnxt()
{
	int k=-1;
	int i=0;
	nxt[0]=-1;
	while(i<s.size())
	{
		if(k==-1 || s[k]==s[i])//判断-1的情况和相等的情况
		{
			k++;
			i++;
			nxt[i]=k;
		}
		else
			k=nxt[k];//回溯
	}
}

KMP函数

int KMP(string ss)
{
	int i=0,j=0;
	int len1=ss.size();
	int len2=s.size();
	while(i<len1 && j<len2)
	{
		if(j==-1 || ss[i]==s[j])
		{
			i++;
			j++;
		}
		else
			j=nxt[j];//回溯
	}
		if(j==len2)//匹配成功
			return i-j+1;
		else//匹配失败
			return -1;
}

main函数

int main()
{
	string ss;
	cin>>ss; 
	cin>>s;
	getnxt();
	cout<<KMP(ss);
	return 0;
}

如果看不懂的话,这里推荐一篇博客
https://blog.csdn.net/starstar1992/article/details/54913261/
超级详细,再看不懂我就吃电脑
就到这里了拜拜ღ( ´・ᴗ・` )比心

  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值