KMP算法--字符串匹配问题

        参考文章:python之KMP算法(原理详解)_kmp算法代码python-CSDN博客

图解KMP算法,带你彻底吃透KMP-CSDN博客

        B站视频:【最浅显易懂的 KMP 算法讲解】https://www.bilibili.com/video/BV1AY4y157yL?vd_source=c8da04bb60a93f6134c95538c2559fb1

做题困扰 

        首先第一次接触到关于字符串匹配的问题,我第一时间想的就是python中的字符串查找index函数,比如说给你一个被查找字符串 s = 'ababcabcacbab' ,查找字符串 t = 'abcac',就可以实现如下操作:

s = 'ababcabcac'
t = 'abcac'
print(f't出现的位置: {s.index(t)})

***
t出现的位置: 5
***

        如果题目上要求一些其他条件就可以在这个基础上进行改进,就可以在处理长字符串检索时加一些条件进行逐一匹配。

        同样的,还有一种朴素的匹配算法:

def search(s, t, m, n):
    i, j = 0, 0
    while i < m and j < n:
        if s[i] == t[j]:
            i += 1
            j += 1
        else:
            i = i - j + 1
            j = 0
    if j == n:
        return i - j
    return 0


s = 'ababcabcac'
t = 'abcac'
print(f't出现的位置: {search(s, t, len(s), len(t))}')

***
t出现的位置: 5
***

        这两种方法原理其实都一样,都是通过逐一字符串匹配,进行目标检索,其时间复杂度非常大。因此,学习了一种减少时间复杂度的算法--KMP算法。

KMP算法简介

        KMP算法(Knuth-Morris-Pratt算法)是一种用于在字符串中查找子串的高效算法。它的主要思想是利用已经部分匹配的信息来避免无效的回溯,从而实现更快的字符串匹配

        KMP算法的核心是构建一个部分匹配表(也称为Next数组),用于记录在匹配过程中已经匹配的部分,从而在匹配失败时可以快速地确定应该将模式串向右移动的位置。

具体来说,KMP算法的实现步骤如下:

  1. 构建部分匹配表(Next数组):遍历模式串,对于每个位置记录其前缀子串中的最长相等前缀后缀的长度。
  2. 匹配过程:在文本串中从左到右逐个字符地与模式串进行匹配。如果当前字符匹配成功,则继续比较下一个字符;如果匹配失败,则根据部分匹配表确定模式串向右移动的位置。
  3. 匹配成功:当模式串完全匹配文本串中的子串时,匹配成功,返回匹配的起始位置。
  4. 匹配失败:如果模式串未能完全匹配文本串,则根据部分匹配表确定模式串的移动距离,继续匹配。

KMP算法的时间复杂度为O(m+n),其中m为模式串的长度,n为文本串的长度。相较于朴素的字符串匹配算法,KMP算法避免了大量的重复比较操作,因此具有更高的效率。        

        正所谓,基础不牢地动山摇,next就是整个算法的基础。其中next数组的求法也涉及到了前后缀的求法。

next数组求法

        next的本质就是一个前缀表,前缀表是一张表(或者直接理解为一个数组),表中记录的是字符串中的每个子串的最大相等前后缀的长度。举个例子:

假如有一个目标字符串 T :‘abcab’,其前缀表用next来表示:

1. 前一个是 a 无前后缀,长度为 0

2.前两个是 ab ,前缀为a ,后缀为b。前后缀不相等,长度也为 0。

3.前三个是 abc, 前缀为 a,ab;后缀为c,bc。前后缀不相等,长度为 0。

4.前四个是 abca, 前缀为a,ab,abc;后缀为a,ca,bca。前后缀最长相等长度为 1。

5.前五个是 abcab,前缀为a,ab,abc,abca;后缀为b,ab,cab,bcab。前后缀最长相等长度为 2。

所以可以得到next数组:

Tabcab
next00012

        既然我们得到了next数组,那么KMP算法已经掌握一大半了。next数组就决定着字符串匹配过程中指针回溯的位置。

那么接下来用代码实现如何求得next数组:

def build_next(patt):
    next = [0]  # next数组 初始值为0
    prefix_len = 0  # 当前共同前后缀的长度
    i = 1
    while i < len(patt):
        if patt[prefix_len] == patt[i]:
            prefix_len += 1
            next.append(prefix_len)
            i += 1
        else:
            if prefix_len == 0:
                next.append(0)
                i += 1
            else:
                prefix_len = next[prefix_len - 1]

    return next


t = 'abcab'
print(f'next: {build_next(t)}')

***
next: [0,0,0,1,2]
***
  • 首先建一个next数组,初始值为0,就是前一个a的前后缀长度。
  •  predix_len = 0,即为当前前一个a 的前后缀长度
  • i = 1 ,初始指针为字符串第二个字符
  •  接下来进行while 循环,直到指针指向最后一个为止
  • 如果当前指针指向的字符patt[i] 和 patt[predix_len]相等,那么就说明 前 i+1 个字符的前后缀最长相等长度为 predix_len+1(比方说前俩字符为‘aa’,此时 i 指向第二个a,predix_len指向的是第一个,他俩相等,就说明aa的前后缀相等,分别为a,a。)所以next.append(predix_len+1),(next就变成了[0,1]),i + 1继续进行判断下一个字符的前后缀最长相等长度(这一步分比较抽象,大家学习的时候可以在本上画图演示一遍)
  • 如果不相等的话,进入else语句里面,再进行①判断prefix_len是否为0,(比方说字符串为‘ab’,此时 i 指向b,predix_len指向a,他俩不相等,同时predix_len为 0 ,那么就是说这个字符串前后缀长度为0),如果为0,i + 1进行下一个判断;②如果不为0,predix_len变为前一位字符串的最大相等长度。
  • 最后经过循环,返回next数组。

        整个过程可能会有些抽象,接下来我用图表的格式来进行一个简单演示。

 

KMP代码实现 

def build_next(patt):
    next = [0]  # next数组 初始值为0
    prefix_len = 0  # 当前共同前后缀的长度
    i = 1
    while i < len(patt):
        if patt[prefix_len] == patt[i]:
            prefix_len += 1
            next.append(prefix_len)
            i += 1
        else:
            if prefix_len == 0:
                next.append(0)
                i += 1
            else:
                prefix_len = next[prefix_len - 1]

    return next


def kmp(target, patt):
    next = build_next(patt)
    i = 0
    j = 0
    while i < len(target):
        if target[i] == patt[j]:
            i += 1
            j += 1

        elif j > 0:
            j = next[j - 1]
        else:
            i += 1
        if j == len(patt):
            return i - j

t = 'abcabc'
s = 'asabdababcabc'
print(f't 在 s 的 位置 {kmp(s,t)}')

***
t 在 s 的 位置 7
***

        整个KMP算法中,通过 匹配过程中省去重头开始匹配的时间,只需每次匹配失败时求出next中next[j-1]的值,使之与i对应再次进行匹配,直至匹配成功。

        接下来,用图来演示整个流程:

总结:

        经过查了可多资料,以及看了好多视频,开始看了好多理论知识,完全搞不懂,特别是求next的那一部分,但是看过那个图解kmp以后,感觉kmp算法也不是那么难。因此我也是,做了很多图解,能够更加清晰地了解kmp。

        我觉得最难的还得是next的求法,如果这些图解也搞不懂,可以举个例子,在演草本上把每个流程写下来,写完一遍就会把这个算法吃透了。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值