ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

KMP

2020-06-19 10:05:04  阅读:486  来源: 互联网

标签:字符 匹配 后缀 next KMP Next 失配


KMP算法

目录

问题

例如:在文本串S(长度为N)中查找模式串P(长度为M)出现的第一个位置 (从0开始)。如果不存在,则返回 -1。

img

Brute Force

一种暴力做法就是从txt的首字符开始配对txt和pat,配对不成功就从txt的下一个字符开始再做一次

时间复杂度:O(MN)

空间复杂度:O(1)

KMP

简介

Knuth-Morris-Pratt 字符串查找算法,简称为 KMP算法,常用于在一个文本串 S 内查找一个模式串 P 的出现位置。这个算法由 Donald Knuth、Vaughan Pratt、James H. Morris 三人于 1977 年联合发表,故取这 3 人的姓氏命名此算法。

img

算法逻辑

假设现在文本串S匹配到 i 位置,模式串P匹配到 j 位置

  • 当前字符匹配成功(即S[i] == P[j],图一),都令i++j++,继续匹配下一个字符;
  • 当前字符匹配失败(即S[i] != P[j],图二),则令 i不变,j = next[j]。此举意味着失配时,模式串P相对于文本串S向右移动了j - next [j]位。

换言之,当匹配失败时,模式串向右移动的位数为:失配字符所在位置 - 失配字符对应的next 值(next 数组的求解会在下文详细阐述),即移动的实际位数为:j - next [j],且此值≥1。

BF匹配失败,就把模式串P右移一位,而KMP匹配失败后,模式串P会移动 j - next [j] 位,缩短了时间是空间换时间的做法。

什么是next数组

原理:

取出文本串 S 和模式串 P 匹配成功的部分,计算这部分前后缀的公共元素,实际就是用模式串 P 的前缀去匹配文本串 S 的后缀。

列举出模式串P匹配文本串S过程中可能出现的几种情况以及对应匹配好的部分

序号 匹配情况
1 第一位失配
2 第二位失配 a
3 第三位失配 a b
4 第四位失配 a b a
5 第五位失配 a b a a
6 第六位失配 a b a a b
7 第七位失配 a b a a b c
8 第八位失配 a b a a b c a

定义:

​ 前缀为除了最后一个字符以外,一个字符串的全部头部组合;

​ 后缀为除了第一个字符以外,一个字符串的全部尾部组合。

以第6种失配情况为例枚举出前后缀。

a b a a b
前缀
a
a b
a b a
a b a a
后缀
b
a b
a a b
b a a b

最长的前后缀的公共元素为ab,长度为2

对所有匹配情况下做相同的操作,记录每个最长前后缀的公共元素长度。

失配位置索引 0 1 2 3 4 5 6 7
失配字符 a b a a b c a c
前后缀公共元素最大长度 0 0 0 1 1 2 0 1

我们将其称为next数组或者fail数组,记录着字符串匹配过程中,发生字符失配时可以从那个位置开始重新匹配。

怎么求next数组?

下面用图示来说明:

当前匹配位置索引为 I (I=J+1),已知next[J]=K,想求出next[I]

  • 红色:P[J]字符失配时,前后缀的公共元素。长度为**next[J] **
  • 蓝色:P[J]字符
  • 橙色:P[K]字符

如果P[J] = P[K],则皆大欢喜, next[I]= next[J]+1
如果P[J] !=P[K],只能寻找更短的相同前后缀匹配,我们看下图

  • 灰色:P[K]字符失配时,前后缀的公共元素。长度为**next[K] **
  • 紫色:P[next[K]]字符
  • 蓝色:P[J]字符

因为红色部分是已经匹配好的,两部分红色都可以是灰+红+灰

如果P[J] = P[next[K]],则皆大欢喜, next[I] = next[K]+1 = next[ next[J] ]+1
如果P[J] !=P[next[K]],继续寻找更短的相同前后缀匹配

结论:求解某个位置(Inext值是一个循环过程,不断检查 上一位置(J)与其(J)最长前缀的后一位(K)是否相等。如果相等next[I] = next[J] + 1,否则 K = next[K]。通过递归索引K = next[K],就能找到长度更短的相同前缀后缀

	n = len(S)
        m = len(P)
        # ———— 构建next ————————————————————————————————————————————
        Next =[0,0]
        k = 0
        for i in range(2,m):
            j = i-1
            k = Next[j]
            # 匹配失败,递归k寻找更小的前缀,直到为0
            while k and P[k] != P[j]:
                k = Next[k]
            # 匹配成功
            if P[k] == P[j] :
                k+=1
                Next.append(k)
            else:
                Next.append(0)

next数组的优化

比如,如果用之前的next 数组方法求模式串“abab”的next 数组,可得其next 数组为0 0 0 1,当它跟下图中的文本串去匹配的时候,发现S[3]P[3]失配。

于是去匹配S[3]P[1]

还是失配,事实上,S[3]P[3]失配,拿与P[3]相等的P[1]去继续匹配S[3],结果肯定是失配。

所以写出next数组的时候要检查一遍,如果P[I] =P[next[I]],递归next[I] = next[next[I]]

所以,咱们得修改下求next 数组的代码。

# —————— 优化next——————————————————————————————————————
        for i in range(2,m):
            j = i-1
            l=Next[i]
            while l and P[i]==P[l]:
                l=Next[l]
            if P[i] != P[l]:
                Next[i] = l
            else:
                Next[i] = 0

最终代码

class Solution:
    def strStr(self, S: str, P: str) -> int:
        n = len(S)
        m = len(P)
        # 特别地,模式串为空字符
        if m == 0:
            return 0
        # ———— 构建next ————————————————————————————————————————————
        Next =[0,0]
        k = 0
        for i in range(2,m):
            j = i-1
            k = Next[j]
            # 匹配失败,递归k寻找更小的前缀,直到为0
            while k and P[k] != P[j]:
                k = Next[k]
            # 匹配成功
            if P[k] == P[j] :
                k+=1
                Next.append(k)
            else:
                Next.append(0)
        
        # —————— 优化next——————————————————————————————————————
        for i in range(2,m):
            j = i-1
            l=Next[i]
            while l and P[i]==P[l]:
                l=Next[l]
            if P[i] != P[l]:
                Next[i] = l
            else:
                Next[i] = 0
        # —————— 开始匹配——————————————————————————————————————
        # i:文本串匹配字符索引
        # j:模式串匹配字符索引
        j = 0
        for i in range(n):
            # 匹配不成功,递归j,直到j=0
            while j and S[i] != P[j]:
                j=Next[j]
            if S[i] == P[j]:
                j+=1
            #退出匹配询问
            if j == m:
                return i-j+1
        return -1

参考资料

https://zhuanlan.zhihu.com/p/76348091

https://www.jianshu.com/p/a9b270eb09bb

https://www.cnblogs.com/zhangtianq/p/5839909.html

https://zhuanlan.zhihu.com/p/76348091

标签:字符,匹配,后缀,next,KMP,Next,失配
来源: https://www.cnblogs.com/jiannan-blog/p/13136778.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有