美文网首页
理解 KMP 算法

理解 KMP 算法

作者: millions_chan | 来源:发表于2017-04-10 22:07 被阅读37次

1. 概述

字符串是编程中常用的一种数据结构,在各个方面都有广泛的应用,而字符串的一种基本操作就是给定一段长度为N的文本,而后给定一段长度M的pattern字符串,在文本中找到和该模式相同的子字符串。

模式 -> N E E D L E

文本 -> I N A H A Y S T A C K N E E D L E I N A

解决这个问题有一种简单的方法:

  1. 从文本的第一个字符开始,逐一的与模式字符串的每个字符进行比较,如果找到完全符合的,查找结束
  2. 在某个位置失配,则从文本的下一个字符串开始处重复步骤1的操作

这种方法在大多数情况下都能良好的工作,然而在一些极端情况下运行时间可能会和MN成正比,如:

模式 -> A A A A B

文本 -> A A A A A A A A A A A A A A A A B

为了解决这个问题,Knuth, Morris和Pratt发明了一种快速查找子字符串的算法,保证最坏情况下运行时间为O(N), 这种算法被称为KMP算法。

2. KMP 算法

2.1 基本思想

该算法的基本思想是,当出现不匹配当字符串时,我们已经知晓了一部分文本当内容,我们可以通过这部分信息减少比较的次数,如当字母表中只有两个字符A,B时:

模式 -> B A A A A A B

当在第六个字符位置匹配失败,那么我们肯定可以知道文本当前六个字符是B A A A A B,那么我们无需再从第二个字符比起,而是可以从文本的第7个字符开始,与模式的2到7个字符比较,如果其7-13个字符是A A A A A B,则找到了符合模式的子字符串,这样就能减少一次比较。

上面的模式有什么规律嘛?那就是由于模式字符串的开头和结尾处有相同的字符串 B ,所以可以跳过这段相同的字符串。如果模式字符串中已经和待搜索字符串有有9个字符 ABCDEFABC 匹配时,如果第 10 个字符失配,就可以快速的跳过模式的前3个字符,比较模式的第 4 个字符 D 是否和待搜索字符串中当前字符相同。为了记录模式字符串的特性,我们需要记录一些额外的数据,所以从某种角度说 KMP 可以认为是一个用空间换时间的算法,只不过由于一般模式字符串都比较短,所以消耗的额外空间很小。

2.2 next 数组的计算

int[] makeNext(String pat)
{
    int q,k;//q:模版字符串下标;k:最大前后缀长度
    int m = pat.length;//模版字符串长度
    int[] next = new int[pat.length];
    next[0] = 0;//模版字符串的第一个字符的最大前后缀长度为0
    for (q = 1,k = 0; q < m; ++q)//for循环,从第二个字符开始,依次计算每一个字符对应的next值
    {
        while(k > 0 && pat.charAt(q) != pat.charAt(k))//递归的求出P[0]···P[q]的最大的相同的前后缀长度k
            k = next[k-1];          //这个while循环是整段代码的精髓所在
        if (pat.charAt(q) == pat.charAt(k))//如果相等,那么最大相同前后缀长度加1
        {
            k++;
        }
        next[q] = k;
    }
}

解释一下上面到代码,q代表当前已经计算到模板的第q个位置,k为位置q之前的最大相同前后缀长度。

那么当第k+1个字符(位置 k 处的字符,注意下标由 0 开始)与第q个字符相等时,我们就可以确定当前位置的最大前后缀长度应当为k+1.

然而,当第k+1个字符与第q个字符不相等时,应该如何处理呢?我们知道此时pat.charAt(k)已经和pat.charAt(q)失配了,然而pat.charAt(q-k) ··· pat.charAt(q-1)又与pat.charAt(0) ···pat.charAt(k-1)相同,那么我们如果我们能找到0到k-1内的最大前后缀字符串,这个字符串肯定也和q-k到q-1的最后一段相同,此时我们就可以继续看看这个字符串的后面一个字符是否和pat.charAt(q)相同了,如果不相同,则可以继续重复这个步骤直到 k = 0,说明没有共同的前后缀,必须从模式的第一个字符开始比较。

得到next数组后,匹配的过程就很简单了:

如果匹配到文本到某个位置i的时候失配了,此时的模式指针值为j,那么从next就能读出此时的最长前后缀长度,也即是下一个比较的j的值(j应该在最长前缀子串的后面一位, 因为数组下标从0开始,所以两者正好相同),如下图:

说明

这里在 j=6 处失配,next[5] 为 2,所以将 j 改为 2

说明2

这里在 j=2 处失配,next[1] 为0,所以 j 改为0:

说明3

这里 j 为 0 时就不匹配,需要将 i 加1。

重复以上步骤就能找到相应的子字符串。

相关文章

  • 对KMP算法的一些理解

    最近学到KMP算法,下面讲讲对KMP算法的一些个人理解,希望对大家有帮助! 对于KMP算法的理解: 整个KMP算法...

  • KMP算法学习札记

    参考文章 知乎:如何更好的理解和掌握 KMP 算法?从头到尾彻底理解KMPKMP 算法(1):如何理解 KMP(原...

  • KMP算法 理解与实现

    KMP算法,背景不必多说,主要想写一写自己对KMP算法的一些理解和其具体实现。关于KMP算法的原理,阮一峰老师的这...

  • 深入理解KMP算法

    深入理解KMP算法 时间:20180313 KMP算法的核心是 求公共最大前后缀。 画图分析 继续分析如何利用前后...

  • KMP算法

    参考文献1.B站灯笼哥2.KMP算法python实现3.如何更好的理解和掌握 KMP 算法?

  • 数据结构与算法14-字符串匹配与KMP

    什么是KMP KMP算法是在字符串匹配算法中比较绕的.主要是需要理解KMP中next数组求解的必要性以及j 的回溯...

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

    什么是KMP KMP算法是在字符串匹配算法中比较绕的.主要是需要理解KMP中next数组求解的必要性以及j 的回溯...

  • KMP 专题整理

    KMP 学习记录 kuangbin专题十六——KMP KMP 学习总结 朴素 KMP 算法 拓展 KMP 算法(E...

  • 理解 KMP 算法

    1. 概述 字符串是编程中常用的一种数据结构,在各个方面都有广泛的应用,而字符串的一种基本操作就是给定一段长度为N...

  • 理解KMP算法

    简单理解KMP 本文读者可以获得两方面的知识 直观理解如何生成部分匹配表(下文简称匹配表),这是KMP算法的核心思...

网友评论

      本文标题:理解 KMP 算法

      本文链接:https://www.haomeiwen.com/subject/ifpeattx.html