1 KMP算法是干嘛的?
我们在查找字符串的子串的时候,往往采用暴力匹配的方法进行字符串的匹配。也即,当模式串和字符串不匹配时,就退回到字符串当前匹配的后一个字符,从头开始匹配。这样的时间复杂度为O(m*n)。实在太慢啦!
那么有没有办法可以降低复杂度,最好在字符串上的指针都不用回退的,这样的时间复杂度就可以大大降低!
KMP算法就是干这事儿,它表示爷从来不走回头路。
那么,KMP算法是如何做到的呢?
2 KMP算法的整体实现
大名鼎鼎的KMP算法,其实是靠一个小小的next数组来实现的,让我们先抛开next数组怎么来的,先来看看KMP算法的整体实现过程。
By the way, next数组在整体实现中的定义是,当前指针的前一个数组下标对应的值,就是子串可以跳过的步数。
让我们来举个栗子:
我们匹配到C发现行不通,那么就看上一个next数组的元素,是2,那么好,我们直接啪的一声很快,跳过两步:
好家伙,这是不是就匹配上了,神奇吧!
那么next数组是怎么的出来的呢?它又是为什么可以跳过元素呢?
3 Next数组的原理
我们已经知道next数组可以跳过前面的元素,那么这是为什么呢?如果我们仔细观察模式串的数值,就不难发现:实际上跳过的元素个数正是相同的前后缀的长度。
更精确地说,Next数组中的数值,正是当前下标与下标0组成的模式子串中,相同前后缀的最大长度。由于后面若干个元素与前面若干个元素相同,因此前面的可以直接跳到后面去!这就是next数组这么能跳的本质!
4 Next数组的计算
知道了Next数组的定义,那么计算出Next数组的值也就顺理成章了。在Next数组的计算中,我们不难发现有两种情况:
4.1 前一个元素相同,下一个元素也相同
毫无疑问,这种情况下,next[i] = next[i - 1] + 1;
4.2 前一个元素相同,下一个元素不相同
这种情况有点棘手,难道我们又要开始暴力回头了吗?不,真男人从不回头,还记得前缀与后缀相同吗?既然整体的后缀已经没办法和前缀相同了,那么一部分的后缀和一部分前缀有没有可能相同呢?
我们可以明显发现,是有可能的,那么从前缀的哪一部分开始匹配呢?我们发现,右边的前后缀其实就等同于左边的前后缀。那么其实等于从左边的相同位置开始查,也就是next[2]的位置。
next[2] = 1,我们能跳一步,从next[1]开始比较。发现相同,那么就next[2]+1=2。如果还不相同,那就再跳,是的还可以跳。