【数据解构】KMP 算法

KMP 算法详解

什么是 KMP 算法

  • KMP 算法是一种改进的字符串匹配算法,即可以快速的从主串中找到子串的算法,由 D.E.Knuth,J.H.Morris 和V.R.Pratt 提出的,因此人们简称为
    KMP算法。
  • KMP 算法的核心是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。

KMP 算法实现思路

  • 根据模式串计算一个next数组。这个数组的每个元素表示字符匹配失败时,应该将模式串的下标移动到那个位置。
  • 检索匹配,当某个字符匹配失败时,然后根据next数组的值更新模式串匹配的下标位置。这样就会快速完成匹配检索。

KMP 算法详解

计算next数组

  1. 核心思想:
    求解最长相等前后缀的长度。

  2. next数组表示的含义:
    当使用模式串去匹配时,字符不匹配时,应该将模式串匹配的位置回溯到某个位置。

  3. next数组的定义:
    next数组的长度等于模式串的长度,next中的每个元素的值是相等前后缀的最长长度。

  4. 理解next数组中的值:

    • 当前元素匹配失败,那么当前元素之前的模式子串是匹配的。
    • 在匹配的子串中,找出所有相等的前后缀,取最长的前后缀子串长度作为当前元素的next值。

    根据以上定义,假设模式串为ababaaab,那么模式串的next数组值如下:

    下标01234567
    模式串ababaaab
    next-10012311

    其中next[3] = 1表示在下标 3 位置匹配失败,这时可以将位置移动到 1,即子串aba的最长相等前后缀a的长度。你可以按照此方法类推其他位置匹配失败时的值。

  5. 代码实现。

    • 根据模式串计算next数组。

      def get_next(t: str):
         """求取 next 数组。
      
         :param t: 输入的模式串
         :return: 返回 next 数组。
      
         用法:
         >>> t="ababaaab"
         >>> get_next(t)
         [-1, 0, 0, 1, 2, 3, 1, 1]
         """
         # 模式串的长度
         l = len(t)
         # 初始化 next 数组,第一个元素默认使用 -1
         next = [-1] * l
      
         # 模式串的下标
         j = 0
         # 相等前后缀的最长值
         k = -1
      
         # 计算。因为每一步赋值都是在 j++ 之后,所以 j 要控制在 l-1 之内
         while j < l - 1:
             # 当相等的前后缀长度为 -1 或者 j 位置和 k 位置的字符相等时, 进行自增,并填写 next 的值。
             if k == -1 or t[j] == t[k]:
                j += 1
                k += 1
                next[j] = k
             # 不相等时,更新 k 的值,这一步很巧妙。不匹配时,要移动到 k 位置继续比较,这时需要将 k 更新为 k 位置的最长前后缀,然后继续比较,因为 K 前边的字符肯定是匹配的。
             else:
               k = next[k]
         return next
      
    • 优化改进。
      用上面的模式串例子说明怎样优化改进。下面是求得的next数组值:

      下标01234567
      字符ababaaab
      next-10012311

      当在 2 位置时匹配失败,那么应该回到 0 位置继续匹配,但 0 位置的字符与 2 位置的字符相同, 那么移动到 0 位置比较,肯定也不匹配,这时需要继续移动,所以可以直接将 2 位置的的值更新为 0 位置的 next 值,相当于回溯位置的值如果与当前位置的字符相同,那么就可以将回溯值直接调整到回溯位置的next值。

      下面是代码实现:

      def get_next_val(t: str):
         """优化 next 数组
         :param t: 模式串
         :return: 返回优化后的 next 数组。
      
         用法:
         >>> t="ababaaab"
         >>> get_next_val(t)
         [-1, 0, -1, 0, -1, 3, 1, 0]
         """
         l = len(t)
         nextval = [-1] * l
      
         # 字符串的起始位置
         j = 0
         # 最长前后缀长度
         k = -1
      
         while j < l - 1:
             if k == -1 or t[j] == t[k]:
                 j += 1
                 k += 1
                 if t[j] == t[k]:
                     nextval[j] = nextval[k]
                 else:
                     nextval[j] = k
             else:
                 # 要移动到 k 位置进行匹配,那么 k 位置之前的最长相等前后缀就是现在匹配时的前后缀初始值。
                 k = nextval[k]
         return nextval
      

检索

求出了next数组,检索就很容易了。下面是具体的代码实现:

def KMP_index(s: str, t: str):
    """KMP 算法实现,从 s 中查找模式串 t。

    :param s: 输入的字符串。
    :param t: 模式串。
    :return: 返回匹配的模式串下标。

    用法:
    >>> s = "Who do created KMP algorithm? It is very beautiful."
    >>> t = "? It"
    >>> KMP_index(s, t)
    28
    """
    nextval = get_next_val(t)
    s_len = len(s)
    t_len = len(t)
    i = 0
    j = 0
    while i < s_len and j < t_len:
        if j == -1 or s[i] == t[j]:
            i += 1
            j += 1
        else:
            # 字符未匹配,则移动模式串的下标。
            j = nextval[j]

    return i - t_len if j >= t_len else -1

上述代码就是 KMP 算法的实现,如果字符串的长度为m,模式串的长度为n,那么这个算法的时间复杂度为 O ( m + n ) O(m+n) O(m+n),这是因为字符串仅仅遍历了一次,求next数组的时间复杂度是 O ( n ) O(n) O(n),所以总的时间复杂度为 O ( m + n ) O(m+n) O(m+n)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值