在讲KMP之前,我们先看看粗暴的字符串匹配算法。
比方说在原串abacbcabababbcbc中找模式串abababb。
其实就是从原串的第一个元素开始一个个对应比较,但凡发现了不匹配,就回头来从原串中第二个元素开始重复前面比较过程。
KMP
这种算法看起来很简单,但是效率却不高,于是有人在想,能不能减少比较次数,于是这个被称为改进的模式匹配算法出来了。
为了让大家不放弃,先不列出公式,用图形来概述下。
如果已知下图中原串与模式串中前4位(绿色部分)相同,那么直接可以比较第5个元素。
再看另一种场景,同样的道理:
也就是说,只要我们可以确定原串中待比较的元素S前面的元素与模式串中待比较元素P前面的元素相同,则直接比较S与P就可以了。
所以核心问题是:如何跳过已知相同的元素比较。
模式串的分析
下面我们单独来分析模式串。
下图表示模式串中01组成的串与34组成串相同,现在我们要比较第5个元素。
如果原串与上面的模式串中0到4个元素已经通过比较,只有第5个不一样。
那么下次比较时我们就可以直接把原串中不通过的元素(红色)直接与模式串中的元素2进行比较,从而跳过了0和1(虚线部分)。
为什么可以这样?
因为由0到4个元素通过比较可知:
A = P[3], B = P[4]
而模式串中P[0] = P[3],P[1] = P[4]。
所以A = P[0] , B = P[1] 。
同理,下图中7比较失败后我们再把原串中元素与3比较就可以了。
综上,我们似乎找到了一个规律:找到模式串中比较失败位置为j,看下它前面有多少字符与模式串最前面的元素相同,比如有k个相同的,那下一趟比较的位置就是k。
如上图中,012与456相同,即3个元素相同,所以下一趟比较的元素就是3,为了最大化地跳过相同元素,k肯定取最大值。
公式
通过上面的分析,这个公式看起来就能理解了。
先看中间那个:
绿框框表示对应元素相等,如P[0} = P[j-k],找到最大的k就行了。
当j=0时,表示原串中的元素与模式串中的第0个就不符合,所以直接下一趟了,所以-1可以用来表示走下一趟。
j为其他情况,即表示待比较的元素不是第0个,且他前面的元素没有相同的,这样只能从头比较了。
靠谱吗?
那么这种直接跳元素的做法,会不会漏?
比如上图中0-4比较通过,第5个比较失败,按KMP做法,会直接把S[5]与P[2]比较,那么可能会错过以S[1]、S[2]、S[3]、S[4]为起点的比较趟次。
如下图为以S[1]为起点的趟次比较。
其实不是错过,是没必要。
下面以S1为例给出证明。
由第0趟比较结果可知:
1) S[0] = P[0],S[1] = P[1],S[2] = P[2],S[3] = P[3],S[4] = P[4]。
如果以S[1]为首的趟次也能比较成功的话,则有:
2) S[1] = P[0],S[2] = P[1],S[3] = P[2],S[4] = P[3],S[5] = P[4] ,S[6] = P[5]
结合1)和2)得,S[0] = S[1] = S[2] = … S[5] = P[0] = P[1] = P[5]。
这与第0 趟中S[5]不等于P[5]矛盾。
其中起点的可相应比较。
到此,关于KMP的我们就讲完了,