通俗解析KMP算法和next函数(小学生都能懂!!!!)

1 目标

搞任何算法前我们需要搞清楚目标,我们的目标是:

  • 给定一个文本串和一个模式串,从文本串里面找第一次出现的模式串。
  • 特别地,找不到就返回-1,模式串为空则返回。
  • 例如:kmp(‘aadaadaaf’, ‘aadaaf’) = 3

2 概念

我们学习模式串匹配文本串之前,必须明确几个重要的概念:

  1. 文本串:一个长字符串,里面可能有模式串,如aadaadaaf。
  2. 模式串:一个比文本串短的字符串,它可能蕴含在文本串中,如aadaaf。
  3. 前缀:一个字符串的子串(去除最后一个字符)。如aad的前缀:a, aa
  4. 后缀:一个字符串的子串(去除第一个字符)。如dac的后缀:c, ac
  5. 最长相等(公共)前后缀:一个字符串里前后缀相等,最大的前缀或后缀的长度。例如:adada,a和a是相等前后缀,ada和ada也是。但是最长的那个是后者,因此是3,

注意!请思考:按照定义,特别地,字符串’a‘有前后缀吗?

3 难点

  1. 暴力匹配:我相信大家都会暴力匹配吧,怎么做呢?其实就是我们人工手动匹配的方法,只是我们的大脑快速运转而已。。例如,文本串:aadaadaaf 子串:aadaaf,当匹配到文本串的第二个d的时候,发现于子串不相同,这个时候,暴力就是直接把子串的第一个a再次与文本串的第二个a比照,然后遇到不相同的时候,再从子串的第一个开始比…其实大家都懂,那我就不赘述。
  2. KMP算法:kmp算法想要告诉我们的是:如果子串具有部分相同的前后缀,那么我们可以跳过某些部分的比较,它是怎么做的呢?还是上面那个例子:匹配到子串的f的时候,你会发现aadaa其实是由相等的前后缀aa的,这意味着什么呢?意味着我可以直接跳过子串前缀的比较,直接开始匹配子串前缀后面的部分这里理解了就好办!看下图:
    在这里插入图片描述
    在圈中的红框内,有最长相同前后缀2,也就是前后两个aa,试想一下,我第一次匹配前缀aa的时候,不就是同时也在进行后缀aa的匹配吗?(继续看下图)
    在这里插入图片描述

按照kmp算法,我们下一步是如上图所示的。我们直接就把模式串的第一个d和文本串的第二个d比较了,注意:这个红色框位置没有变哦你发现没有,kmp算法没有比较1号位置的aa,为什么呢?就像第一张图片说的,我已经知道红色框部分是前后缀相同的了。也就是说:文本串中,在红框中的这五个字符组成的字符串必定也是前后缀相同的。也就是说:我们只要把模式串前缀这一坨(绿色波浪线)移动到与文本串中红色框的这一坨后缀(2号蓝色波浪线)重贴即可,并且不需要再匹配这一坨,因为我们已经知道这一坨就是相等的前缀和后缀!

  1. next数组:next数组是能够实现kmp算法的关键,它直接揭示了模式串中的前后缀相等情况。

4 代码解析

 # 复杂度:O(m)
def get_next(s):
    ls = len(s)
    next_list = [0 for i in range(ls)]
    j, i = 0, 1 # 定义前缀末尾,后缀末尾(只有前两个字符的字符串有这个概念!)
    for i in range(1, ls):               # 后缀末尾i是一直+1的
        while(j > 0 and s[i] != s[j]):   # 如果遇到不相等的字符:回退
            j = next_list[j-1] 
        if s[i] == s[j]:                 # 如果遇到相等的字符:记录
            j += 1
        next_list[i] = j # 注意:j扮演两个角色:1. 以索引j为结尾的前缀的最大相同前后缀长度;2. 索引i处匹配不正确时应该回退的索引位置 
        # 3. 仔细想想索引i匹配不正确时候,不就是应该要回退到前缀的后一个字母吗?因为相同部分(相同前后缀)肯定相等啊(节约了时间!)
    return next_list
    
def kmp(s, pattern):
    if not pattern: return 0
    ls, lp = len(s), len(pattern)
    nex = get_next(pattern) # 获取next数组
    j, i = 0, 0
    while(i < ls):
        # 如果不相等
        if s[i] != pattern[j]:
            if j == 0: 
                i += 1
            else:
                j = nex[j - 1]
        
        else: # 如果相等
            i += 1
            j += 1
        
        # 如果j是最后一个,则匹配成功
        if j == lp: return i - lp
        
    return -1
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值