V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
zjsxwc
V2EX  ›  程序员

kmp 算法用动态规划思想更容易理解

  •  
  •   zjsxwc ·
    zjsxwc · 2021-02-16 10:38:54 +08:00 via Android · 1197 次点击
    这是一个创建于 1170 天前的主题,其中的信息可能已经有所发展或是发生改变。
    kmp 算法的核心是计算 Next 数组,但是网上流传的实现代码比较晦涩,这里用动态规划来计算 Next 更加容易理解。

    #### 预先定义

    P[] 为长度为 n 的匹配字符串,index 从 0 开始

    Next[] 为要计算的数组,Next[j] 表示长度为 j 的字符串"P[0]P[1]...P[j-1]"的前缀等于后缀的最长长度,比如对于字符串 P 为"abCdabCe"来说其 Next[7]为 3 因为 abC 的长度为 3, 边界值 Next[0] = 0, Next[1] = 0

    动态规划状态函数为 f(int j, char x),f(j,x)表示长度为 j+1 的字符串"P[0]P[1]...P[j-1]x"
    的前缀等于后缀的最长长度,比如对于字符串 P 为"abCdabCe"来说其 f(6, 'C')为 3 因为 abC 的长度为 3,很直接的我们可以知道 Next[j] = f(j-1, P[j])


    #### 状态转移方程的三个情况

    f(j, x) =

    1. 当 x 等于 P[Next[j]+1] 时,f(j, x) = Next[j] + 1

    2. 当 x 不等于 P[Next[j]+1] , 且 j 为 0 时, 这就是边界条件,此时 f(j, x) = 0

    3. 当 x 不等于 P[Next[j]+1] , 且 j 不为 0 时,f(j, x) = f(Next[j], x)

    #### 额外的解释说明

    上面条件 1 与 2 很好理解,这里需要对条件 3 稍作解释。

    我们先定义一个字符串上的操作 K,
    使 K(P) = Next[len(P)],也就是对 P 取最长前缀后缀相同长度。

    对于匹配字符串 P"eaabCaeae"来说 Next[8]=2 因为"ea"长度为 2,当我们需要计算 Next[9]也就是 f(8, e)时 由于字符 e 不等于字符 a,所以条件 1 不满足,而由于我们知道 Next[8]是代表字符串"ea"是长度为 8 的子字符串"eaabCaea"的最长相同前缀与后缀长度,这里为了方便说明,我们给这两个前缀后缀分别命名为 pre8 与 suf8,pre8 与 suf8 都是长度为 2 的字符串"ea",   我们要计算 Next[9]也就是 f(8, e)也就是相当于求新后缀字符串 "${suf8}e"的 K, 这是因为最末字符 e 的存在,如果可求 K 大于 0 那么另一个字符 e 必然也存在于 suf8 中,而 K("${suf8}e") 等于 K("${pre8}e")  等于 f(len(pre8), e) 等于 f(Next[8], e)


    参考 https://www.cnblogs.com/dusf/p/kmp.html
    第 1 条附言  ·  2021-02-24 21:50:07 +08:00
    更详细点解释,

    我们要计算 Next[9]也就是 f(8, e)也就是相当于求新后缀字符串 "${suf8}e"的 K, 这是因为最末字符 e 的存在,如果可求 K 大于 0 那么另一个字符 e 必然也存在于 P 字符串的前缀 pre8 中,而 pre8 等同于 suf8,所以另一个 e 也必然被包含于 suf8 之中,而 K("${suf8}e") 等于 K("${pre8}e")  等于 f(len(pre8), e) 等于 f(Next[8], e)
    3 条回复    2021-02-24 21:52:16 +08:00
    iFlicker
        1
    iFlicker  
       2021-02-16 12:21:24 +08:00 via Android
    万物皆可 dp😂
    zjsxwc
        2
    zjsxwc  
    OP
       2021-02-24 21:46:32 +08:00 via Android
    我们要计算 Next[9]也就是 f(8, e)也就是相当于求新后缀字符串 "${suf8}e"的 K, 这是因为最末字符 e 的存在,如果可求 K 大于 0 那么另一个字符 e 必然也存在于 suf8 中,而 K("${suf8}e") 等于 K("${pre8}e") 等于 f(len(pre8), e) 等于 f(Next[8], e)
    zjsxwc
        3
    zjsxwc  
    OP
       2021-02-24 21:52:16 +08:00 via Android
    由于二楼这句里面有点含糊,所以我 append 了更详细点的解释。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   2376 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 12:41 · PVG 20:41 · LAX 05:41 · JFK 08:41
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.