【算法技术专题】精彩解密KMP算法之跃进式搜索的深度探索

KMP算法

  • KMP算法介绍
    • KMP算法历史
    • KMP算法思路
    • 性能损耗
    • 算法思路
      • 结构模型准备
      • KMP算法的实现步骤
        • 生成next数组
          • 构建next数组原理
            • 生成nexf数组代码实现
          • 代码案例解释说明
        • 字符串对比操作
          • 代码案例解释说明
    • 算法效果

KMP算法介绍

KMP算法(Knuth-Morris-Pratt算法)是一种字符串匹配算法,用于在一个字符串S内查找另一个字符串P的出现位置。KMP算法的核心思想是利用已经匹配过的部分信息,尽量减少不必要的字符比较,从而提高匹配效率。

KMP算法历史

KMP算法是一种字符串匹配算法,它的全称是Knuth-Morris-Pratt算法,由Donald Knuth、Vaughan Pratt和James Morris三位计算机科学家于1977年联合发明。KMP算法的核心思想是利用已知信息避免无用的字符比较,从而提高字符串匹配的效率。

KMP算法思路

KMP算法的主要思路是,对于模式串P和文本串T,我们从T的开头开始逐个字符比较,如果当前字符匹配成功,则继续比较下一个字符;如果匹配失败,则根据已知信息跳过一些比较,从而减少不必要的字符比较,提高匹配效率。

性能损耗

KMP算法解决的问题是字符匹配,这个算法把字符匹配的时间复杂度缩小到O(m+n),而空间复杂度也只有O(m),n是target的长度,m是pattern的长度。

算法思路

结构模型准备

当我们在字符串S中匹配模式串P时,KMP算法中的第一步是根据模式串P生成一个next数组。next数组的定义是,对于模式串P中的每一个位置i,next[i]表示以i为结尾的子串中,最长的既是前缀又是后缀的字符串的长度。

KMP算法的实现步骤

下面我们来具体讲解KMP算法的实现步骤。

生成next数组

例如,对于模式串P=“ababcabab”,其next数组为[0,0,1,2,0,1,2,3,4]。

构建next数组原理
  • next[0]和next[1]都为0,因为以P的第0个位置和第1个位置结尾的子串中,没有既是前缀又是后缀的字符串。

  • next[2]为1,因为以P的第2个位置结尾的子串中,"ab"既是前缀又是后缀,长度为1。以此类推,直到next[8]为4,因为以P的第8个位置结尾的子串中,"abab"既是前缀又是后缀,长度为4。

生成nexf数组代码实现

生成next数组的具体实现方法如下:

def get_next(p):
    """
    生成next数组
    """
    n = len(p)
    next = [0] * n
    j = 0
    for i in range(1, n):
        while j > 0 and p[i] != p[j]:
            j = next[j-1]
        if p[i] == p[j]:
            j += 1
        next[i] = j
    return next
代码案例解释说明

p为模式串,n为模式串的长度,next为生成的next数组。

我们从模式串的第1个位置开始遍历,如果当前字符与前一个字符相等,则next[i] = next[i-1] + 1;否则,我们需要找到以i-1为结尾的子串中最长的既是前缀又是后缀的字符串,这个字符串的长度就是next[i-1]。

接着,我们从这个字符串的下一个字符开始继续比较,直到找到一个字符与当前字符相等或者已经没有可比较的字符为止。如果找到了一个字符与当前字符相等,则next[i] = next[i-1] + 1;否则,next[i] = 0。

字符串对比操作

有了next数组之后,我们就可以在S中匹配P了。具体地,我们从S的开头开始逐个字符比较,如果当前字符匹配成功,则继续比较下一个字符;如果匹配失败,则根据next数组跳过一些比较。

具体来说,设当前匹配到S的第i个位置,P的第j个位置,若S[i]和P[j]不匹配,则根据next[j]的值,将j跳到next[j]位置继续匹配。这里的跳跃是利用了P中已经匹配过的部分信息,尽量减少不必要的字符比较,从而提高匹配效率。

当j跳到0位置时,表示匹配成功,返回 i-j 的值即为匹配位置在S中的起始下标。如果S中没有匹配成功的位置,则匹配失败。

具体实现代码如下:

def kmp(t, p):
    """
    在文本串t中匹配模式串p
    """
    n, m = len(t), len(p)
    next = get_next(p)
    j = 0
    for i in range(n):
        while j > 0 and t[i] != p[j]:
            j = next[j-1]
        if t[i] == p[j]:
            j += 1
        if j == m:
            return i - m + 1
    return -1
代码案例解释说明

t为文本串,p为模式串,n和m分别为文本串和模式串的长度,next为模式串的next数组。我们从文本串的第1个位置开始遍历,如果当前字符与模式串中对应的字符相等,则继续比较下一个字符;否则,我们根据模式串的next数组跳过一些比较。如果j跳到0位置,则表示匹配失败;如果j跳到模式串的末尾,则表示匹配成功,返回匹配位置在文本串中的起始下标。

算法效果

KMP算法的时间复杂度为O(m+n),其中m和n分别为P和S的长度。因此,KMP算法在处理长字符串匹配时具有较高的效率。

部分匹配表(Next数组):表的作用是 让算法无需多次匹配S中的任何字符。能够实现线性时间搜索的关键是 在不错过任何潜在匹配的情况下,我们"预搜索"这个模式串本身并将其译成一个包含所有可能失配的位置对应可以绕过最多无效字符的列表。

你可能感兴趣的:(底层服务/编程功底系列,算法)