Loading

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]

整理得:

\[next[j] = j - ((j-1)-pi[j-1]) = pi[j-1] + 1 \]

这个1有时加有时不加,如果是选择题的话,我们主要看next[1]是0还是-1

通过上述分析,我们可以得到next函数的公式

\[ next[j]= \begin{cases} 0,j=1 \\ max\{k|1<k<j且'p_1 \dots p_{k-1}'='p_{j-k+1} \dots p_{j-1}'\},当此集合不为空时 \\ 1 其他情况 \end{cases} \]

基于此我们来讨论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数组的过程也是线性的,这个结论考研不涉及,我们不证。感兴趣的读者可以去了解一下势能复杂度分析

posted @ 2024-09-30 21:09  AH20  阅读(10)  评论(0编辑  收藏  举报