一、什么是kmp算法?
去百度上搜素一下,你会得到下面一段话:
KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt提出的,因此人们称它为克努特—莫里斯—普拉特操作(简称KMP算法)。KMP算法的核心是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是通过一个next()函数实现,函数本身包含了模式串的局部匹配信息。
乍一看,是不是有点懵逼?什么next函数、什么局部匹配,我没看懂啊! 没关系,看了我的这篇博文,一定帮你把kmp算法安排的明明白白。
二、实例介绍
很多人想学kmp算法,可能是因为遇到字符串匹配问题:
有一个字符串str1 = “bbc abcdab abcdabcdabde” 和 一个字串 str2 = “abcdabd”
现在判断str1是否包含str2,如果存在,就返回第一次出现的位置,如果没有,就返回-1.
遇到这种问题,如果之前没学过kmp算法,肯定第一反应:暴力匹配法。就是一个个字符进行对比,直到每一个字符相同为之。但是,你也应该能意识到,这样会特别的耗时,而且程序会非常的不简洁。
那我们就先通过实现暴力匹配法,引出我们的kmp算法。
三、暴力匹配法的实现思路及其代码演示
实现思路:
- 假设我们给定现在str1 匹配到 i 位置,str2 匹配到 j 位置。如果当前字符匹配成功(str[i] = str[j]) ,则i++、j++,继续匹配下一个字符。
- 如果匹配失败。(str[i] != str[j]) 令 i = i -(j-1),j = 0; 就相当于匹配失败以后,i回溯,j重新被赋值为0.
注:暴力匹配法可能思路上唯一难点就是这个了。 - 整体的思路就是这样,可以看出。每当我们匹配不成功以后,就会重新回溯一遍,相当的耗时耗力。
代码演示:
每一步都有详细的注释,应该挺容易理解的。
介绍完暴力匹配法,下面我们就来讲讲kmp算法。
四、kmp算法的实现思路
依然用上面那个例子
1.首先str1的第一位与str2的第一位进行比较,如果不合适,则往后移一位:
2.重复第一步,不合适,继续往后移:
3.直到str1的第一个字符与str2的第一个字符相匹配:
4.接着比较,还是符合:
5.直至匹配到两个位置字符不相符为止:
6.如果按照我们之前暴力匹配法的方式,肯定是将str1后移一位,然后与str2重新进行比较。这样是非常不明智的,因为BCD之前已经比较过了。当空格与D不匹配时,你已经知道前面六个字符是ABCDABD。kmp算法的思路是,设法通过这种已知的信息,不要把“搜索位置”移回到已经比较过的位置,继续把他往后移,这样就提高了效率。
我的理解是这样的,比如:你的一个子字符串是“A”开头,那么当你第一次匹配不成功时,往后移,移到下一个”A“开始,进行下一次匹配。那么你怎么知道到底移动几位,到达下一个A的位置呢?这是就要引入一个公式
移动位数 = 以匹配字符数 - 对应的部分匹配值
什么是部分匹配值
这就涉及到前缀与后缀的问题。
什么叫前缀与后缀?
比如说:有一个字符串 ABCDE
那么他的前缀就是去掉E 得到 A、AB、ABC、ABCD
后缀就是去掉A 得到 BCDE、CDE、DE、E
当前后缀有一个相等时,共有元素的长度如果为 1 那么部分匹配值就是 1 …
所以我们很容易的知晓:
A 没有前缀与后缀 所以他的部分匹配值是 0
AB 他的前缀是 A 后缀是 B 所以部分匹配值是 0
ABC 他的前缀是 A AB 后缀是 BC C 所以部分匹配值也是 0
ABCD 他的前缀是 A AB ABC 后缀是 BCD CD D 所以部分匹配值也是 0
ABCDA 他的前缀是 A AB ABC ABCD 后缀是 BCDA CDA DA A 有一个相同,其长度为1,所以部分匹配值是 1
ABCDAB 他的前缀是 A AB ABC ABCD ABCDA 后缀是 BCDAB CDAB DAB AB B 有1个相同,其长度为2,所一部分匹配值是 2
ABCDABD 他的前缀是 A AB ABC ABCD ABCDA ABCDAB 后缀是 BCDABD CDABD DABD ABD BD D 所以部分匹配值为 0
得到一个字串的部分匹配值以后
我们将以匹配的位数(如题目所示,六位相同,所以为6)减去 对应的部分匹配值(即最后一位匹配数,题目上是D 对应的是 2 )
这样后移4位 进行下一次比较~
口说无凭,上代码~
五、kmp算法的代码演示
代码还是比较简单的,主要的难点,在我看来就是kmp算法核心 那一块的代码,我也进行了注释
从代码中,我们不难看出,用kmp算法解决这些问题,大大减少了代码的时间复杂度,使代码运行的效率更高。真的很不错~
大家多多理解,一起加油呀~