KMP算法
前言
从一个字符串中找到特定的子字符串的朴素解法就是暴力搜索每一种每一种可能,直至找到,运用该种暴力搜索的时间复杂度为O(len1 * len2),不难看出随着字符串与子字符串的长度变长,运行时间爆炸式增长。于是我们需要寻找一种时间复杂度低的算法来解决这类问题,而KMP算法便是这样一种高效的算法,其时间复杂度为O(len1 + len2)。
题目:
首先参考来自Leetcode的一道关于字符串匹配的的算法题:
分析题目,题目需要我们在原字符串中找到子字符串并且输出子字符串出现在字符串中的第一个位置。不难看出,这就是一道关于字符串匹配的题目,我们优先介绍暴力搜索:
一、暴力搜索实现
暴力搜索的思路:从字符串中找到所有与子字符串长度相等的字符串与子字符串进行比较,输出完全匹配的字符串输出最小的起始点即可。
代码如下:
字符串的前len1 - len2个字母均有可能成为长度为len2的字符串,于是需要遍历前len1 - len2 个点,而对于每个长度为len2的字符串都需要和子字符串进行比较,于是很容易得到其时间复杂度为O((len1 - len2) * len2)。这种时间复杂度无法满足我们对大量字符串匹配的工作,于是KMP算法被发明出来。
二、KMP算法实现
KMP算法的具体代码如下:
不难分析出,KMP算法中创建next数组的时间复杂度为O(len2),利用next数组进行匹配的时间复杂度为O(len1),于是KMP算法的时间复杂度为O(len1 + len2),相较于暴力搜索高效。
三、KMP算法思路
接下来我们开始顺着KMP算法的思路进行探索
STEP1:知道为什么KMP比暴力搜索高效
首先看暴力搜索:(假设len1 = 9,len2 = 6)
即以字符串第i个位置为起点截取len2长的子字符串与子字符串匹配失败时,再以第i+1个位置为起点进行匹配。
再看KMP算法:
KMP算法利用前一次已经匹配成功的不完全子字符串(ACBAC)中寻找有用信息(AC),利用使得起点直接从跳跃至第四个位置,极大地加快了匹配速度。
STEP2:知道匹配成功的不忘全子字符串中哪些是有用信息
我们看到匹配成功的不完全子字符串其前缀与后缀一致,那么最大的前后缀所对应的字符就是有用信息。因为其最大前后缀一致就意味着其后缀就是匹配字符串的前缀,那么只需要从该点进行匹配即可,最大后缀前的点必定不可能匹配出匹配字符串。
STEP3: 如何利用有用信息
当我们在某个位置无法匹配成功时,我们就需要利用该有效信息,找到匹配字符串下一个利用的匹配点,从该匹配点开始匹配,前面的字母由于前缀等于后缀的有效信息已经成功确立匹配了。
STEP4: 如何确立有效信息
由于可能在匹配字符串的每一个位置出错,所以我们应该创立一个和匹配字符串同等大小的数组 next [ ] 记录下当每一个位置出错时我们应该开始的下一个匹配点。
建立next数组:(其实就是确立为匹配完全的字符串中最大公共前后缀的长度极为该位置的next数组中的值)
STEP5:利用next数组进行匹配字符串
至此,KMP算法完结。
可以看到,KMP算法在暴力搜索的基础上,充分利用了已匹配成功的不完全字符串中的有用信息,排除点了根本不可能的起始点位置,从而加速了起始点的行进过程,进而使得字符串的匹配速度加快。KMP算法的核心在于对于匹配成功的不完全字符串的有效信息的充分提取,也就是在于next [ ] 数组的建立,要想完全掌握KMP算法,我们最应该掌握的就应该是next [ ] 的建立过程。
next [ ] 的建立过程中其实利用到了递归(动态规划)的思想,即使得某一位置的下一个匹配点可能还会有下一个匹配点,这是候就要利用前面位置的下一个匹配点,这就展示了动态规划的思想。