KMP算法详解
简介
- 从一个字符串中查找另一个字符串的位置
- 会利用字符串匹配过程中失败和成功的信息,减少字符串查找比较的字数
前置知识
- 前缀:包括一个字符串第一个字符的子串;真前缀指除了它本身的所有前缀
- 后缀:包括一个字符串最后一个字符的子串;真后缀指除了它本身的所有后缀
核心思想
- 在s1中查找s2出现的位置,我们将s1称作待匹配串,s2称作模式串
- 当一次匹配失败时,找到模式串中失配位置,此时我们可以知道,失配位置之前的这个子串是匹配成功的,我们将这个子串命名为s3
- 接下来我们考虑如何向后移动子串,寻找下一个可能匹配的位置,现在我们已清楚的是:s3就是待匹配串的一面镜子,我们接下来尝试去匹配的部分就在它的后缀之中,而在模式串中我们首先要确保它的前缀与待匹配串适配,如果接下来我们在s3中寻找它的前缀可以和它的后缀匹配的最长部分(长度为l),并在下一次移动中使s3的前缀与移动之前的这个后缀重合,这样就找到了最近的一个可能匹配的位置
- 我们想想为什么这是最近的,即证移动到它之前的位置都一定无法匹配成功:如果移动到之前的位置可以匹配成功,那么意味着s3的前缀可以和后缀匹配成功的长度要大于l,但l已经是我们之前找到的最大长度,故产生矛盾,得证
- 再代入极端条件理解一下:若b=0,则下次从s2[0]开始匹配;实际情况下,找不到前缀与后缀的重叠部分,则无法找出已经可以匹配的前缀部分,则要从s2的开头开始匹配。符合实际情况
- 不难发现前面所说的s3就在模式串的前缀集合中。现在我们只需解决子问题:寻找模式串中所有前缀的真前缀与真后缀最长匹配部分,这部分称为最长border
子问题
寻找模式串中所有前缀的真前缀与真后缀最长匹配部分
令模式串 0~i 子串的最长 border 的长度为 b_i。
我们可以找到 b_i 与 b_{i-1} 的递推关系:
如果 s[i] = s[b[i-1]],则 b[i] = b[i-1] + 1。
否则,我们要寻找 0~(i-1) 中次短 border 的长度,看能否匹配。又
s[0] ~ s[b[i-1]-1] = s[i-b[i]] ~ s[i-1]
s[0]s[i-1] 的后缀等于 s[0]s[b[i-1]-1] 的真后缀,
前缀也是 s[0]s[b[i-1]-1] 的真前缀,
故问题转化为寻找 s[0]s[b[i-1]-1] 的最长 border(注意此处所说的前缀、后缀都指要找的次短 border),
亦即 b[b[i-1]-1],再看 s[i] 是否等于 s[b[b[i-1]-1]],继续迭代即可。
这样我们就可以递推地标出数组 b!
代码实现
- 代码实现中,将模式串的指针设为j,待匹配串的指针设为i,指向要匹配的字符,若失配则将j设置为b[j-1], 从这一位开始匹配即可,因为前面的都已匹配好
- 洛谷P3375【模板】[[KMPhttps://www.luogu.com.cn/problem/P3375]]
1 |
|
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 Spring Garden!