id="c_n9"width="1920"height="990"style="position: fixed; top: 0px; left: 0px; z-index: -1; opacity: 0.5;">

KMP

KMP

算法思路

有如下情况(这里原串&子串下标都是从1开始)

原串\(s\)(以下简称\(s\))和子串\(p\)(以下简称\(p\))进行匹配,直到黑色分界线时都是匹配的,直到其后面一个元素不相等,\(s[i] \ne p[j + 1]\)

屏幕截图 2023-03-15 211305

首先在这样的情况下,我们可以通过移动\(p\)来实现\(s[i] = p[j + 1]\);如果直接回溯\(s\)的话,效率过低。

图如下:

屏幕截图 2023-03-15 212432

那么这样就可以运用到之前已经匹配了的\(s\),这里不难看出,这个\(p\)移动的距离就是\(next[j]\);而通过观察发现这个\(next[j]\)就是寻找到一个与以j为后缀的串相等的最长的前缀串(不包含其自身);所以那么\(s\)\(p\)匹配的过程中若不匹配了,那么\(p\)就回退到\(next[j]\),然后再进行比较,直到退无可退或者是已经匹配了。当\(j = len(p) - 1\)时,就表示匹配成功,同时将\(j = next[j]\)(因为匹配成功后,之后的情况就是不匹配了,与之前的情况类似,所以也需要回退一下)

match code

//  返回匹配成功后,开始进行匹配的位置
//  因为在匹配演示的过程中是j + 1 和 i进行比较的,故s从1开始,p从0开始
func match(s, p []byte) (res []int) {
    // n表示原串的长度,m表示子串或者模式串的长度
    n, m := len(s), len(p)
    for i, j := 1, 0; i < n; i++ {
        for j != 0 && s[i] != p[j + 1] {
            j = next[j]
        }
        if s[i] == p[j + 1] {
            j++
        }
        if j == m - 1 {
            res = append(res, i - j + 1)
            j = next[j]
        }
    } 
    return
}

当然还需要预处理出\(next\)数组,仔细分析一下,其实和match的过程是类似的。当\(p[i] \ne p[j + 1]\),那么也就开始移动\(p\)串,直到其为相等或者是到退无可退的情况,从而修改\(next[i]\)中的值;

pre-treatment code

// 从2开始是因为next[1]是退无可退的情况,故next[1] = 0;所以从2开始即可
// 在golang中初始化即为0,故不需要写出;对于c/c++这类需要初始化或初始化为全局变量
func preTreatment(p []byte) []int {
    m := len(p)
    next := make([]int, m)
    for i, j := 2, 0; i < m; i++ {
        for j != 0 && p[i] != p[j + 1] {
            j = next[j]
        }
        if p[i] == p[j + 1] {
            j++
        }
        next[i] = j
    }
    return next
}

Q&A

Q:为什么在pre-treatment中不需要特判数组的长度?

A:因为\(next[i]\)寻找的是与以i结尾的后缀相等的最长的前缀,所以\(i\)的取值从某种意义上来看是不会超过模式串的长度的;故不需要该特判,省略掉即可。

参考资料

posted @ 2023-03-15 22:00  hellozmc  阅读(17)  评论(0编辑  收藏  举报