KMP算法

目录

 

一、引言,何为KMP算法

二、暴力算法

三、过渡(思路)

四、具体代码实现


 

一、引言,何为KMP算法

        KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt提出的,因此人们称它为克努特—莫里斯—普拉特操作(简称KMP算法)。KMP算法的核心是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是通过一个next()函数实现,函数本身包含了模式串的局部匹配信息。KMP算法的时间复杂度O(m+n)

下面开始介绍KMP算法,在此先规定好文章中所用变量代表意义:
字符串A为被查找对象,字符串B为查找对象,即在A中寻找B是否存在
数组 next 为字符串B经过KMP算法之后得到的用于记录指针移动的数组
设字符串A的长度为m,字符串B的长度为n
用#代表一个为止的字符
k为头字符串指针,j为尾字符串指针

二、暴力算法

遇到字符串匹配问题(在字符串A中查找字符串B是否存在),很多初学者第一时间想到的就是遍历整个字符串A。当遇到A、B首字母相同的时候开始遍历字符串B判断是否相同,若不相同则字符串B向后移动,直到遇到下一次A、B首字母相同再次开始遍历,最后发现相同的时候退出。

 78c3fb94632a479dab615e4fa40b533a.gif

图1

乍一看,对于上图两个字符串A、B,似乎暴力算法也不算很麻烦,但是请看下图的两个字符串

2b5f431591334f6285049ce471ea6a8b.gif

图2

对于字符串B,每次需要遍历完整个字符串才会发现最后一个不相同,于是字符串B向后挪动一位继续重新开始比较,于是可以得出整个算法时间复杂度大概为O(m * n),当m、n非常大时间可以看出我们浪费了很多不必要的时间

三、过渡(思路)

不难发现对于图2中的两个字符串,浪费我们时间的主要因素有两点:
1.对于A中的每一个字母都可以和B的首字母匹配,于是每个A中的字符都可以作为B的起点,总共     需要比较m次。
2.对于B中的首字母之后的字母恰好可以和A中充当首字母的之后的字母匹配上。
因素一对应时间负责度O(m* n)中的m,而因素二则对应n

于是得到这样一个思路,既然字符串B前部分aaa相同,只有最后的b是不匹配的,那么我们是不是可以把字符串B向后移动一个字符,然后从第三个字符开始比较呢?即字符串A中全部是a,我们只需要在其中找到一个字符b即可判断字符串B存在,如下图:

e07006b8fa81436aa8905e8cadc29ff6.gif

图3

 这样一来便不再需要比较 m * n 次,我们只需要判断字符串B向后移动一位之后,A中的第四个字符能否和B中的第三个字符匹配。若能,则将A之前的第四位字符看做当前的第三位字符,向后寻找新的第四位字符。
更通俗的解释就是:
当前字符串A是 aaa#1#2,已知#1不是b,那么若#1是a,则字符串A变成功aaaa#2,此时去掉第一个a,即(a)aaa#2,现在只需要判断#2是不是b,若是则B存在,若不是则重复上述步骤

于是整体思路已确定,接下来的问题是如何判断数组B的指针在不匹配之后应该指向哪里,这个过程只需要字符串B,即每个字符串B有且仅有唯一对应的数组next。
先继续从图3的字符串B入手,得出数组B的核心在于,判断最长相同头字符串、尾字符串,过程如下:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5r-hIOeZvQ==,size_20,color_FFFFFF,t_70,g_se,x_16

当指针指向第三个字符的时候判断第二个字符前面的部分“a1a2”,最长相同头字符串、尾字符串是“a1”和“a2”,即当第三个字符不匹配的时候,返回到第一个字符之后(为什么没有判断前两个字符的原因稍后会解释)

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5r-hIOeZvQ==,size_20,color_FFFFFF,t_70,g_se,x_16

 当指针指向第四个字符的时候判断第二个字符前面的部分“a1a2a3”,最长相同头字符串、尾字符串是“a1a2”和“a2a3”,即当第三个字符不匹配的时候,返回到第二个字符之后

为了实现上述过程,我们需要两个指针,一个为最长相同头字符串,一个为最长相同尾字符串,当两个指针指向的字符相同的时候同时向后移动,并记录下之前相同的长度,整体过程如下

968e4ae0a68046468da042c1bfbfebce.gif

图4

值得注意的是,每次判断的过程是,先判断->再移动->移动之后记录,而不是先移动->再判断->再记录,对于上述next数组的意义即,判断B指向的字符和A指向的字符不同的时候,B的指针,返回到对应next记录的下标处。如最后一个字符b不同,那么B的指针指向B[next[3]],即B[2],即a3
而考虑到不是所有B都是k,j都是同时移动的,可能出现k,j指向字符不相等情况,这个时候只有一个指针可以移动,于是考虑到先判断再移动的顺序,将next[0]设为-1,k,j不匹配的时候k = next[k],如此可以防止头字符串中也存在相同头字符串和尾字符串,如图6

以下列举一个k会左移的字符串B求next数组的过程:

c861120cadfc46b3bc237db3ccb7b0ee.gif

 图5

 

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5r-hIOeZvQ==,size_20,color_FFFFFF,t_70,g_se,x_16

图6

 

 

四、具体代码实现

整体思路过程已经确定好了,接下来就是代码的实现,在此以函数的形式写出对next数组赋值的操作。

void GetNext(char* t, int* next)	  //t为字符串B,next为创建的数组next
{
	int j, k, str = strlen(t);        //k为头字符串指针,j为尾字符串指针
	j = 0; k = -1;                    //首先初始化K为-1,因为判断过程先先判断在移动
	next[0] = -1;                     //next[0]为-1是为了之后头字符串的移动
	while (j <  str - 1)
	{
		if (k == -1 || t[j] == t[k])  //先判断
		{
			j++; k++;                 //再移动
			next[j] = k;              //再记录,k指针指向的位置就是AB字符不匹配的时候B指针应当返                    
                                      //回的位置
		}
		else
		{
			k = next[k];              //这一步很关键,表明了不匹配的时候k指针应当回到的位置
                                      //具体可以参考图5和图6
		}
	}
}

 在此附上力扣题目的链接,感兴趣可以去尝试以KMP写法提交:https://leetcode-cn.com/problems/implement-strstr/

 

 

  • 5
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值