KMP算法学习的一点个人理解

本文深入讲解KMP算法的原理及Python实现,通过实例演示next数组生成过程,剖析算法设计灵感,提供完整代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

PS:KMP算法的背景相关请自行百度,本篇编程语言使用python,个人学习用,理解上可能存在谬误。算法实现出自本人自以为理解的KMP而码成,不保证正确性以及性能最优。

假设有两个字符串s, r,求r在s中出现的位置,效率最高的算法似乎就是KMP了。

1.生成next数组

算法的第一步是生成r的next数组,next[i],其中0<=i<len(r),的值即为子串r[:i+1]的最大的相同前后缀长度减去1。换一种理解,next[i]即为子串r[:i+1]中最大的相同前后缀中最大前缀的末尾的索引

用一个例子说明,若r = "ababc"

next[0],即子串"a"的最大的相同前后缀长度显然是0.因为"a"的长度为1,是不可能有前缀或后缀的,故值为-1

next[1],即子串“ab”,同样,也不存在相同的前后缀,值为-1

next[2],即子串"aba",此时,最大的相同前后缀显然就是"a",所以next[i]为子串“aba”中最大的相同前后缀中最大前缀“a”的末尾的索引,即0.

next[3],即子串"abab",此时,满足条件最大前缀为"ab",那么next[3]的值为1

next[4],即子串"ababc",此时显然无满足条件的相同前后缀,值为-1

所以,"ababc"的next数组为:[-1, -1, 0, 1, -1]

那么,如何用程序去计算出这个next数组呢?

 

这里面,个人觉得KMP算法中最大的一个亮点,也是算法设计灵感的源泉,来源于这么一个现象:

假设我们已经有了0...i的next值,那么,现在要求next[i+1]的值,

我们先获取next[i]的值,这个值为子串r[:i+1]的最大的相同前后缀中最大前缀的末尾的索引,记为k.

那么r[:k+1] 就等于 r[i-k:i+1],

(*)此时我们考察r[i+1]是否等于r[k+1]:

如果两者相等,那么对于子串r[:i+2]来说,其最大的相同的前后缀即为r[:k+2],故而next[i+1]的值就是k+1

如果两者不等,这里面最有意思的是,我们取k = next[k],然后继续比较r[i+1]是否等于r[k+1], 回到(*)处循环了起来。

 

结合上述例子,看下i=2的时候,此时next[2] = 0, 现在求next[3]的值,

那么k = next[2], 即k= 0,此时考察r[i+1]是否等于r[k+1],即r[3]是否等于r[1],这里两者都是"b",相等,因此next[3] = k + 1,即1.

对照实例,继续理解的话,这个算法理解起来就不难了。

 

这里贴出我理解后的python实现:

def gen_next(r):
    m = len(r)
    if m == 1:
        return [-1]
    next = [-1 for _ in range(m)]
    k = -1
    for i in range(1, m):
        no_match = False
        while r[k +1] != r[i]:
            if k == -1:
                next[i] = -1
                no_match = True
                break
            k = next[k]
        if no_match:
            continue
        k += 1
        next[i] = k
    return next

print(gen_next("ababaca"))

2.KMP算法主体

其实能够手撸next出来的,也很容易就能把这个搜索函数给码出来。

对于从字符串l中搜索子串r, 假设我们在此场景:

当前搜索到l的i位置,即目前扫描到字符l[i],对于r,已经有(k+1)个字符匹配,当前正在扫描r[k+1],并且此时必然有:

r[:k+1]等于l[i-k-1:i]。那么,此时考察l[i]是否等于r[k+1],

(*)若l[i] == r[k+1]:

说明串进一步匹配,i和k更进一步

若不等:

此时取k = next[k],回到(*)处继续判断。

 

这里又出现了k = next[k],这应该就是KMP的灵魂所在了。其实光看文字描述感觉很抽象,但是结合实际例子,便很直观了

这里画了张示意图,其中红色部分为相同串。

 

接下来是最后的python实现:

def gen_next(r):
    m = len(r)
    if m == 1:
        return [-1]
    next = [-1 for _ in range(m)]
    k = -1
    for i in range(1, m):
        no_match = False
        while r[k +1] != r[i]:
            if k == -1:
                next[i] = -1
                no_match = True
                break
            k = next[k]
        if no_match:
            continue
        k += 1
        next[i] = k
    return next


def kmp(l, r):
    l_len = len(l)
    r_len = len(r)
    if l_len == 0 or r_len == 0:
        return
    next = gen_next(r)
    k = -1
    for i in range(l_len):
        no_match = False
        while l[i] != r[k + 1]:
            if k == -1:
                no_match = True
                break
            k = next[k]
        if no_match:
            continue
        k += 1
        if k == r_len - 1:
            return i - k


print(kmp("bacbababadababacambabacaddababacasdsd", "ababaca"))

 

 

后记:根据算法去理解其意图是比较简单的,但是从无到有创造出KMP算法的人真她娘的是个天才。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值