KMP算法
引言
之前在打ACM竞赛时就学过一些字符串相关的算法,其中就包括KMP。但是面向竞赛的KMP算法和面向408的KMP算法在一些概念和实现细节上有细微差异,所以特意写了这篇文章对408中的KMP算法做出总结
字符串的前缀、后缀和部分匹配指
前缀指除了最后一个字符以外,字符串的所有头部子串;后缀指除了第一个字符外,字符串的所有尾部子串,部分匹配值则为字符串的前缀和后缀的最长相等长度
比如字符串aba
,有前缀{ab,a}
,后缀{ba,a}
,二者交集为{a}
,因此最长相等的前后缀长度为1。我们用数组pi[i]
表示长度为i的前缀的部分匹配值
next数组
当发生失配时,假设现在主串匹配到了i,模式串匹配到了j。如果是暴力匹配,我们应该将i回退到i-j+2
,j回退到1
,但是由于前面已经匹配了一部分,所以我们可以不用从头开始匹配。而是将j回退到j-move
,其中move=(j-1)-pi[j-1]
整理得:
这个1有时加有时不加,如果是选择题的话,我们主要看next[1]是0还是-1
通过上述分析,我们可以得到next函数的公式
基于此我们来讨论next数组的意义,next数组提供给我们的信息是,当模式串指针为j发生失配时,下一个应该匹配哪个位置,而next[1]=1则是说,第一个字符失配了,此时应该将i和j都加一
KMP算法可以保证线性复杂度,是因为主串不回溯,也就是说i的值永远不会变小
KMP算法的进一步优化
当\(p_{j} \neq s_{i}\)时,下一步必然发生\(p_{next[j]}\)和\(s_i\)的比较,而当\(p_{j} = p_{next[j]}\)时,这次比较将会变得毫无意义,为了进一步优化该算法,当出现\(p_{j} = p_{next[j]}\)时,需要再次进行递归,将\(next[j]\)修正为\(next[next[j]]\)
后记
KMP算法求next数组的过程也是线性的,这个结论考研不涉及,我们不证。感兴趣的读者可以去了解一下势能复杂度分析