KMP算法

字符串匹配

什么叫字符串匹配或者字符串模式匹配?

假设主串为:“abcdeabcef” 子串为"abce" ,找出子串在主串中第一次出现的位置,即红色部分第一个字符的索引,上述问题就是我们通常说的字符串匹配问题,或者一些要用到上面知识的问题。

朴素算法

这个算法就跟它的名字一样,很朴素,就是理解起来比较简单,大家都会想到的思路,就是主串,子串各自建立一个索引,从下标为0开始比较,如果S[i]=P[i] 则继续比较下一位,当出现不等的情况时,子串整体向右移动一位,注意,是整体右移,与S[1]及其后面的字符开始比较。此处似乎应该放些图,更好理解。




好,前面那些步骤应该是很正常的,也是可以理解的,此时我们已经发现有6个字符是匹配好的,然后朴素算法的下一步却是:如下图

是不是觉得很可惜,觉得子串为什么不往后多移动几个位置,那么,移动几个位置呢?为什么要移动这个数量的位置呢?移动这个数量有什么根据吗?好这就引出了接下来的KMP算法
以上图片来源:http://www.ruanyifeng.com/blog/2013/05/Knuth–Morris–Pratt_algorithm.html

链接中算法原理讲的非常详细,但是个人觉得有点冗长,还是很推荐看一下的。
朴素算法的代码如下:

# -*- coding: utf-8 -*-

def String_Match(string,substring):
    len1,len2=len(string),len(substring)
    i,j=0,0
    while i<len1-len2+1:
        if string[i+j]==substring[j]: #如果相等则一直往后匹配
            if j==len2-1:#当匹配至子串最后一个字符时,返回索引i
                return i
            j+=1
        else:           #匹配失败,则子串整体右移一位
            j=0
            i+=1        
    return -1            #不存在该子串

s='abcdeabcef'
p1='abce'
p2='abef'
print(String_Match(s,p1))
print(String_Match(s,p2))

KMP算法

总的来说,字符串匹配还是经常遇到的,也看到过很多KMP算法思想的介绍,最终根据自己的体会,得到一张图,可能有点抽象,但是个人认为是这个算法的核心了,能看懂最好,看不懂也别怪我了:

图中写的较抽象,勿怪,更专业一点,子串当出现匹配失败时,子串右移位数是由一个叫做next表的东西来决定的,看了不少的博客,这个地方都是讲得似乎比较含糊,要不就是贴一段代码上来,为什么是这样求?怎么可以这样求?求next表才是是KMP算法的关键,因此我画了上面那图来帮助理解。

next表

首先,next表是对于子串而言的,它决定了子串的某一位匹配失败时,应该回退到哪,即这一位匹配失败回退值由这一位的next值决定。通过上面我画的图,相等区域,有个更专业的名称,叫最长公共前后缀,举个例子:

  • abcddabc :最长公共前后缀为 abc
  • efabcdef :最长公共前后缀为 ef
    那么,当我们匹配失败时,(看上面的图的右下角部分),我们从最长公共前后缀的前缀后一位开始继续匹配,因为它前面的都已经是匹配成功的。

next表的建立

next表的由来上面已经解释清楚了,模式串向右移动的位数为:已匹配字符数 - 失配字符的上一位字符所对应的最大长度值 下面通过一个例子来看建立过程
例如子串为: a b c d e a b f

i01234567
子串/模式串abcdeabf
next[i]-10000012
  • next[0]=-1 这个记一下,-1表示不存在相同的最大前缀和最大后缀,模式串首字符的next值为-1

  • next[1],next[2],next[3],next[4],next[5] 值均为 0,因为 i 位前的串最长公共前后缀长度都为0

  • next[6]=1 ,因为substring[6] 前面为 abcdea 最长公共前后缀为 a 长度为 1

  • next[7]=2 ,同next[6]理可得

即next[k]: substring 的前k个元素组成字符串的公共前后缀最大长度
def Get_next(substring):
    n=len(substring)
    next_table=[0]*n
    next_table[0]=-1
    j=0
    for i in range(1,n-1): #i+1最大取到n-1
        while j>0 and substring[i]!=substring[j]:
            j=next_table[j]
        if substring[i]==substring[j]:
            j+=1
        next_table[i+1]=j #next_table[i+1]是前面0到i串的公共缀最大长度
    return next_table

#打印next表
print(next('ABCABD'))
print(next('abcdeabcef'))

在这里插入图片描述

KMP算法代码实现
# -*- coding: utf-8 -*-

def Get_next(substring):
    n=len(substring)
    next_table=[0]*n
    next_table[0],next_table[1]=-1,0
    j=0
    for i in range(1,n-1): #i+1最大取到n-1
        while j>0 and substring[i]!=substring[j]:
            j=next_table[j]
        if substring[i]==substring[j]:
            j+=1
        next_table[i+1]=j #next_table[i+1]是前面0到i串的公共缀最大长度
    return next_table

#打印next表
print(Get_next('ABCABD'))
print(Get_next('abcdeabcef'))

def Kmp_Match(s,p):
    len1,len2=len(s),len(p)
    next_table=Get_next(p)
    i,j=0,0
    while i<len1:
        if s[i]!=p[j] and j>0:
            j=next_table[j]  #子串回退
        else:
            if j==len2-1:     #全部匹配成功
                return i-len2+1 #返回开头处索引
            j+=1
            i+=1
    return -1  #不存在该子串
#测试案例
print(Kmp_Match('abcdeabcef','abce')) # 5
print(Kmp_Match('abcfe','abce'))      # -1 表示不存在该子串
print(Kmp_Match('abcef','abce'))    # 0
print(Kmp_Match('abc','abce'))       #len2 >len1 不存在该子串
print(Kmp_Match('ABABABC','ABC'))     # 4
print(Kmp_Match('aabbABccABCABD','ABCA')) # 8
print(Kmp_Match('1235551234','1234')) # 6
    

如有问题,请随时指出,万分感谢!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值