KMP算法复习

太久没打了,刚好有道题用上了,就复习一下。 我觉得复到KMP应该就够用了,如果要AC自动机我直接死在那里。

参考资料 如何更好地理解和掌握 KMP 算法? - 阮行止的回答 - 知乎 https://www.zhihu.com/question/21923021/answer/1032665486

核心思想

KMP算法要解决的问题是字符串匹配问题。给定原串S和模版串P,求解原串中是否存在子字符串与模版串相匹配。

最简单的想法就是暴力搜索,设原串S长度为n,模板串P长度为m,显然暴力的时间复杂度为O(nm),非常慢。

KMP的想法是充分利用适配信息,其next数组的定义如下:next[i]表示在P[0:i]子串中,使前k个字符恰好等于后k个字符的最大的k,且k不能等于i+1(否则就等于它自己了) 这个数组的定义挺绕的,第一眼基本都不会反应过来,我就不插图了,只用文本表述一下(建议看图)。KMP的失配匹配,本质上就是把模版串向前伸,直到伸到前缀与后缀匹配为止,这实际上就是自己与自己匹配。因此这个k就是前缀与后缀相同的最大长度k。

因此next数组可以用如下方法求得

def getNxt(x):
    for i in range(x,0,-1):
        if p[0:i] == p[x-i+1:x+1]:
            return i
    return 0

nxt = [getNxt(x) for x in range(len(p))]

构建next数组的复杂度显然是O(m^2)的 使用next数组加速匹配

def search(p,s):
    tar = 0 # 主串中将要匹配的位置
    pos = 0 # 子串中将要匹配的位置
    while tar < len(s):
        if s[tar] == p[pos]: # 若两个字符相同,匹配成功,tar和pos各进一步
            tar += 1
            pos += 1
        elif pos>0: # 失配,且pos>0,则next数组移动
            pos = nxt[pos-1]
        else: # pos[0]也失配了,则主串前进
            tar += 1
        
        if pos == len(p): # pos走到了len(p),匹配成功
            print(tar-pos) # 输出起始地点
            pos = nxt[pos-1] # 当作失配,继续下一次匹配

时间复杂度为O(n+m),会比暴力方法小很多。

快速求next数组

这一步也是KMP的精髓,前面我没记错的话应该是MP算法,这一步是K算法。 其核心在于让P自己与自己做匹配。

next数组的定义是使得前缀与后缀的前k个字符相等的最大的k,隐含着一个匹配。因此可以考虑使用next数组递归求解自身

现在考虑两种情况,对于给定字符串

a b c a b d d d a b c a b c
_________ X ----__________X
0 0 0 1 2 0 0 0 1 2 3 4 5 ?

前一个子串为A串,后一个子串为B串在两个X的地方。如果前者等于后者,则?处的next显然等于6,即next[i-1]+1,即得配的情况。 但是现在是失配了,显然目标是要找到一个新的now,使得前面的前缀与后面的后缀相同,而且注意到前面的A串与B串是相同的,因此就可以利用起A串的next数组。

nxt = []
def buildNxt():
    nxt.append(0) # next[0]一定是0
    x = 1 #求解从1开始
    now = 0
    while x < len(p):
        if p[now] = p[x]: #第一种情况,得配,相前一位
            now += 1
            x += 1
            nxt.append(now) # 之前没有,我看了一下意思,我觉得要加上
        elif now>0: # 失配,需要缩小now
            now = nxt[now-1]
        else:
            nxt.append(0) #now最小,此时肯定为0
            x += 1
0%