0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

KMP算法详解

冬至子 来源:数据结构和算法 作者:博哥 2023-06-07 16:23 次阅读

KMP 算法主要用于字符串匹配的,他的时间复杂度 O(m+n) 。常规的字符串匹配我们一般都这样做,使用两个指针,一个指向主串,一个指向模式串,如果他俩指向的字符一样,他俩同时往右移一步,如果他俩指向的字符不一样,模式串的指针重新指向他的第一个字符,主串的指针指向他上次开始匹配的下一个字符,如下图所示。

图片

我们看到当模式串和主串匹配失败的时候,模式串会从头开始重新匹配,主串也会回退,但我们知道失败字符之前的子串都是匹配成功的,那么这个信息能不能被我们利用呢?当然可以,这个就是我们这里要讲的 KMP 算法,他就是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。

当使用 KMP 算法的时候,如果某个字符匹配失败,主串指针不回退,而模式串指针不一定回到他的第一个字符,如下图所示。

图片

当字符 ed 匹配失败之后,主串指针不回退,模式串指针指向字符 c ,然后在继续判断。主串指针不回退好操作,但模式串指针怎么确定是回到字符 c ,而不是其他位置呢?这是因为在字符 e 匹配失败后,字符 e 前面有 2 个字符 ab 和最开始的 2 个字符 ab 相同,所以我们可以跳过前 2 个字符。也就是说模式串匹配某一个字符失败的时候,如果这个失败的字符前面有 m 个字符和最开始的 m 个字符相同,那么下次比较的时候就可以跳过模式串的前 m 个字符,如下图所示。

图片

通过上面的图我们可以知道,当某个字符匹配失败的时候,他前面的肯定都是成功的,也就是说字符串 s1 和字符串 s2 完全一样。在模式串中,匹配失败的字符前面 b4b3 是相同的,所以我们可以得到 b1,b2,b3,b4 都是相同的,也就是说b2b3 也是相同的,既然是相同的,跳过即可,不需要在比较了,直接从他们的下一个字符开始匹配。

KMP 算法的核心部分就是找出模式串中每个字符前面到底有多少字符和最开始的字符相同,我们用 next 数组表示。有的描述成真前缀和真后缀的相同的数量。这里要注意当前字符前面的字符不包含第 1 个字符,最开始的字符也不能包含当前字符,比如在模式串 abc 中不能说字符 c 前面有 ab 和最开始的 ab 相同,来看下表。

1.jpg

我们看到字符 e 前面有 ab 和最开始的字符 ab 相同,长度是 2 。在看第 2a 前面没有(不包含自己)和最开始字符有相同的,所以是 0 。在任何模式串的第 2 个字符前面都没有和最开始字符有相同的,因为前面的是不包含第一个字符,所以 next[1]=0 。如果第 2 个没有,那么第 1 个就更没有了,为了区分,可以让 next[0]=-1 ,当然等于 0 也可以,需要特殊处理下即可。我们先来看下 next 数组怎么求。

图片

使用两个指针 ij ,其中 j 是字符 p[i] 前面与最开始字符相同的数量,也是 next[i] 。如果 p[j]==p[i],也就是说字符 p[i+1] 前面有 j+1 个字符和最开始的 j+1 个字符相同,所以可以得到 next[i+1]=j+1 ,这个很容易理解。如果 p[j]!=p[i],我们让指针 j 等于 next[j] ,然后重新和 p[i] 比较,这就是回退,这个回退也是 KMP 算法的最核心部分,我们来分析下为什么要这样回退。

图片

如上图所示,如果 nxet[j] 大于 0 ,那么 j 前面的 b2b1 是相同的,我们知道 S1S2 也是相同的,所以我们可以得出 b1,b2,b3,b4 都是相同的,如果 p[i]!=p[j] ,只需要让 j 回退到 next[j] ,然后 ij 在重新比较,这个 next[j] 就是 b1 的长度,因为 b1b4 是相同的,所以不需要再比较了。如果还不相同, j 继续回退,直到回退到 -1 为止,我们拿个真实的字符串看下,如下图所示。

图片

我们看到 b1,b2,b3,b4 是相同的,他们都是字符串 "abc" ,如果 p[i]!=p[j] ,我们就让 j 回退到 next[j] ,因为他们前面 b1b4 是相同的,没必须要在比较了,最后再来看下代码。

public int strStr(String s, String p) {
    int i = 0;// 主串S的下标。
    int j = 0;// 模式串P的下标。
    int[] next = new int[p.length()];
    getNext(p, next);// 计算next数组。
    while (i < s.length() && j < p.length()) {
        // 如果j为-1或者p[i]==p[j],i和j都往后移一步。当j为-1时,
        // 说明p[i]!=p[0],然后i往后移一步,j也往后移一步指向p[0]。
        if (j == -1 || s.charAt(i) == p.charAt(j)) {
            i++;
            j++;
            if (j == p.length())// 匹配成功。
                return i - j;
        } else {
            // 匹配失败,j回退,跳过模式串P前面相同的字符继续比较。
            j = next[j];
        }
    }
    return -1;
}

private void getNext(String p, int next[]) {
    int length = p.length();
    int i = 0;
    int j = -1;
    next[0] = -1;// 默认值。
    // 最后一个字符不需要比较。
    while (i < length - 1) {
        // 如果j为-1或者p[i]==p[j],i和j都往后移一步。
        if (j == -1 || p.charAt(i) == p.charAt(j)) {
            i++;
            j++;
            // j是字符p[i]前面和最开始字符相同的数量。
            next[i] = j;
        } else {
            j = next[j];// 回退,KMP的核心代码。
        }
    }
}

KMP优化

我们来看下表 ,当模式串在下标为 4 的位置匹配失败的时候,下一步 j 会回退到下标为 1 的位置,但这两个位置的字符是一样的,既然一样,拿过来比较也是不会成功,所以如果字符一样,我们可以优化一下,往前查找相同的子串。

1.jpg

来看下代码:

private void getNext(String p, int next[]) {
    int length = p.length();
    int i = 0;
    int j = -1;
    next[0] = -1;// 默认值。
    // 最后一个字符不需要比较。
    while (i < length - 1) {
        // 如果j为-1或者p[i]==p[j],i和j都往后移一步。
        if (j == -1 || p.charAt(i) == p.charAt(j)) {
            i++;
            j++;
            // 这里要注意,i和j都已经执行了自增操作。
            if (p.charAt(i) == p.charAt(j))
                next[i] = next[j];
            else
                next[i] = j;
        } else {
            j = next[j];// 回退,KMP的核心代码。
        }
    }
}

如果前面查找的还是相同的,我们该怎么办呢?是不是需要写个递归,继续往前找?实际上是不需要的,比如模式串 "aaaaaaaaaab" ,如果没优化他是这样的。

图片

如果优化之后,当最后一个 a 匹配不成功的时候,他会回退到前面一个 a ,实际上前一个 a 在计算的时候已经回退过了,就是 -1 ,所以不需要递归,直接赋值就行。

图片

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • kmp算法
    +关注

    关注

    0

    文章

    4

    浏览量

    1420
收藏 人收藏

    评论

    相关推荐

    昂宝OB3392KMP 资料

    昂宝OB3392KMP 资料隔离 单级PFC10--30瓦
    发表于 11-17 16:49

    SVPWM的原理推导和控制算法详解

    SVPWM的原理推导和控制算法详解,不错的资料,值得一看
    发表于 01-28 15:09

    详解快速傅里叶变换FFT算法

    本帖最后由 richthoffen 于 2019-7-19 16:41 编辑 详解快速傅里叶变换FFT算法
    发表于 07-18 08:07

    详解快速傅里叶变换FFT算法

    详解快速傅里叶变换FFT算法
    发表于 03-28 11:48

    算法篇(PID详解)

    算法篇(PID详解)
    发表于 05-19 10:30

    详解快速傅里叶变换FFT算法

    详解快速傅里叶变换FFT算法
    发表于 05-25 09:31

    详解快速傅里叶变换FFT算法

    详解快速傅里叶变换FFT算法
    发表于 03-05 11:07

    详解三电平传统SVPWM调制算法原理

      各位同学你们好呀,上期我们讲了中性点钳位型的三电平逆变器原理,相信大家都有印象了。那么这一期我们要详解三电平传统SVPWM调制算法原理。通过学习后,希望能给初学者提供捷径明白算法原理,将来做仿真
    发表于 08-27 07:25

    模式匹配算法的深入研究

    模式匹配算法的深入研究:模式匹配算法的应用较为广泛, KMP算法是一种性能较高的算法,所以对KMP
    发表于 10-25 12:21 13次下载

    路由算法详解

    路由算法详解1. 引言 2. 路由器基础知识 3. LS算法 4. 示例:Dijkstra算法 5. DV算法 6. 分级路由
    发表于 08-06 09:36 5327次阅读
    路由<b class='flag-5'>算法</b><b class='flag-5'>详解</b>

    SVPWM的原理及法则推导和控制算法详解

    SVPWM的原理及法则推导和控制算法详解
    发表于 04-13 15:42 16次下载

    PID算法详解

    PID算法详解
    发表于 12-17 20:48 12次下载

    基于KMP算法的串口通讯协议解析邹铁

    基于KMP算法的串口通讯协议解析_邹铁
    发表于 03-17 08:00 2次下载

    字符串的KMP算法和BM算法

    本文主要介绍KMP算法和BM算法,它们分别是前缀匹配和后缀匹配的经典算法。所谓前缀匹配是指:模式串和母串的比较从左到右,模式串的移动也是从左到右;所谓后缀匹配是指:模式串和母串的的比较
    发表于 11-15 14:53 2293次阅读
    字符串的<b class='flag-5'>KMP</b><b class='flag-5'>算法</b>和BM<b class='flag-5'>算法</b>

    [源代码]Python算法详解

    [源代码]Python算法详解[源代码]Python算法详解
    发表于 06-06 17:50 0次下载