【转载】如果你看不懂KMP算法,那就看一看这篇文章( 绝对原创,绝对通俗易懂)

本文通过实例和图表详细解析KMP算法的核心概念,包括next数组的意义及其求解方法,帮助读者深入理解字符串匹配的强大算法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

如果你看不懂KMP算法,那就看一看这篇文章(绝对原创,绝对通俗易懂)

KMP算法,俗称“看毛片”算法,是字符串匹配中的很强大的一个算法,不过,对于初学者来说,要弄懂它确实不易。整个寒假,因为家里没有网,为了理解这个算法,那可是花了九牛二虎之力!不过,现在我基本上对这个算法理解算是比较透彻了!特写此文与大家分享分享!

我个人总结了,KMP算法之所以难懂,很大一部分原因是很多实现的方法在一些细节的差异。怎么说呢,举我寒假学习的例子吧,我是看了一种方法后,似懂非懂,然后去看另外的方法,就全都乱了!体现在几个方面:next数组,有的叫做“失配函数”,其实是一个东西;next数组中,有的是以下标为0开始的,有的是以1开始的;KMP主算法中,当发生失配时,取的next数组的值也不一样!就这样,各说各的,乱的很!

所以,在阐述我的理解之前,我有必要说明一下,我是用next数组的,next数组是以下标0开始的!还有,我不会在一些基础的概念上浪费太多,所以你在看这篇文章时必须要懂得一些基本的概念,例如“朴素字符串匹配”“前缀”,“后缀”等!还有就是,这篇文章的每一个字都是我辛辛苦苦码出来的,图也是我自己画的!如果要转载,请注明出处!好了,开始吧!

假设在我们的匹配过程中出现了这一种情况:

这里写图片描述

根据KMP算法,在该失配位会调用该位的next数组的值!在这里有必要来说一下next数组的作用!说的太繁琐怕你听不懂,让我用一句话来说明:

返回失配位之前的最长公共前后缀!

好,不管你懂不懂这句话,我下面的文字和图应该会让你懂这句话的意思以及作用的!

首先,我们取之前已经匹配的部分(即蓝色的那部分!)

这里写图片描述

我们在上面说到next数组的作用时,说到“最长公共前后缀”,体现到图中就是这个样子!

这里写图片描述

接下来,就是最重要的了!

这里写图片描述

没错,这个就是next数组的作用了:

返回当前的最长公共前后缀长度,假设为len。因为数组是由0开始的,所以next数组让第len位与主串匹配就是拿最长前缀之后的第1位与失配位重新匹配,避免匹配串从头开始!如下图所示!
这里写图片描述

(重新匹配刚才的失配位!)

如果都说成这样你都不明白,那么你真的得重新理解什么是KMP算法了!

接下来最重要的,也是KMP算法的核心所在,就是next数组的求解!不过,在这里我找到了一个全新的理解方法!如果你懂的上面我写的的,那么下面的内容你只需稍微思考一下就行了!

跟刚才一样,我用一句话来阐述一下next数组的求解方法,其实也就是两个字:

继承

a、当前面字符的前一个字符的对称程度为0的时候,只要将当前字符与子串第一个字符进行比较。这个很好理解啊,前面都是0,说明都不对称了,如果多加了一个字符,要对称的话最多是当前的和第一个对称。比如agcta这个里面t的是0,那么后面的a的对称程度只需要看它是不是等于第一个字符a了。

b、按照这个推理,我们就可以总结一个规律,不仅前面是0呀,如果前面一个字符的next值是1,那么我们就把当前字符与子串第二个字符进行比较,因为前面的是1,说明前面的字符已经和第一个相等了,如果这个又与第二个相等了,说明对称程度就是2了。有两个字符对称了。比如上面agctag,倒数第二个a的next是1,说明它和第一个a对称了,接着我们就把最后一个g与第二个g比较,又相等,自然对称成都就累加了,就是2了。

c、按照上面的推理,如果一直相等,就一直累加,可以一直推啊,推到这里应该一点难度都没有吧,如果你觉得有难度说明我写的太失败了。

当然不可能会那么顺利让我们一直对称下去,如果遇到下一个不相等了,那么说明不能继承前面的对称性了,这种情况只能说明没有那么多对称了,但是不能说明一点对称性都没有,所以遇到这种情况就要重新来考虑,这个也是难点所在。
这里写图片描述
如果蓝色的部分相同,则当前next数组的值为上一个next的值加一,如果不相同,就是我们下面要说的!

如果不相同,用一句话来说,就是:

从前面来找子前后缀

1、如果要存在对称性,那么对称程度肯定比前面这个的对称程度小,所以要找个更小的对称,这个不用解释了吧,如果大那么就继承前面的对称性了。

2、要找更小的对称,必然在对称内部还存在子对称,而且这个必须紧接着在子对称之后。

如果看不懂,那么看一下图吧!
这里写图片描述
好了,我已经把该说的尽可能以最浅显的话和最直接的图展示出来了,如果还是不懂,那我真的没有办法了!

说了这么多,下面是代码实现

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define N 100

void cal_next( char * str, int * next, int len )
{
    int i, j;

    next[0] = -1;
    for( i = 1; i < len; i++ )
    {
        j = next[ i - 1 ];
        while( str[ j + 1 ] != str[ i ] && ( j >= 0 ) )
        {
            j = next[ j ];
        }
        if( str[ i ] == str[ j + 1 ] )
        {
            next[ i ] = j + 1;
        }
        else
        {
            next[ i ] = -1;
        }
    }
}

int KMP( char * str, int slen, char * ptr, int plen, int * next )
{
    int s_i = 0, p_i = 0;

    while( s_i < slen && p_i < plen )
    {
        if( str[ s_i ] == ptr[ p_i ] )
        {
            s_i++;
            p_i++;
        }
        else
        {
            if( p_i == 0 )
            {
                s_i++;
            }
            else
            {
                p_i = next[ p_i - 1 ] + 1;
            }
        }
    }
    return ( p_i == plen ) ? ( s_i - plen ) : -1;
}

int main()
{
    char str[ N ] = {0};
    char ptr[ N ] = {0};
    int slen, plen;
    int next[ N ];

    while( scanf( "%s%s", str, ptr ) )
    {
        slen = strlen( str );
        plen = strlen( ptr );
        cal_next( ptr, next, plen );
        printf( "%d\n", KMP( str, slen, ptr, plen, next ) );
    }
    return 0;
}

如果有什么问题,欢迎评论指正!还是大一新手,很需要进步!

转自:
http://blog.youkuaiyun.com/u011564456/article/details/20862555?utm_source=tuicool&utm_medium=referral

<think>嗯,我现在得理解用户的问题,他想了解KMP算法,而且要通俗易懂,最好有图示。首先,我得回忆一下KMP算法的基础知识。KMP是用于字符串匹配的高效算法,相比暴力匹配,它利用部分匹配的信息避免重复比较。核心是next数组,也就是部分匹配表。 用户可能对暴力匹配的问题不太清楚,可能需要先简单对比一下。比如暴力匹配每次失配时,主串和模式串都回退,导致时间复杂度高,O(mn)。而KMP通过next数组确定模式串该回退的位置,主串不回溯,所以效率更高,O(n+m)。 接下来,我需要解释next数组的作用。这里的关键是理解最长公共前后缀。比如模式串“ABABC”,每个位置的前缀后缀最长共有元素长度是多少?比如在索引3的位置(假设从0开始),子串“ABAB”的最长公共前后缀是“AB”,长度2。这样,当失配时,模式串可以跳到该位置继续匹配,而不是从头开始。 可能需要用例子来演示。比如主串是“ABABABABC”,模式串是“ABABC”。当匹配到第四个字符时发现不匹配,根据next数组,模式串移动两位,继续比较,而不是从头开始。 然后,如何构建next数组?这可能需要分步骤解释。比如初始化、处理前后缀相同和不同的情况。比如,用两个指针,一个从0开始,另一个从1开始,比较字符,如果相同,则next[i] = j+1,同时i和j都增加;如果不同,则j回退到next[j-1]的位置,直到j为0或者找到匹配。 图示的话,可能需要画出一个模式串,标出每个位置的next值,并演示匹配过程。例如,模式串“ABABC”对应的next数组可能是[0,0,1,2,0]。在匹配过程中,当失配发生时,根据next值调整模式串的位置。 用户可能对部分匹配表的具体计算过程感到困惑,这里需要详细解释。比如,对于每个子串,找出其前缀和后缀的最长共有长度。然后,这个长度值就是next数组中的值,但可能需要调整(比如整体右移一位,初始化为-1等,根据实现不同)。 另外,需要指出KMP算法的优势和应用场景。例如,当模式串有较多重复部分时,KMP能显著减少比较次数,适用于文本编辑器中的查找功能或生物信息学中的基因序列匹配等。 最后,要确保解释清晰,避免术语堆砌,用简单例子配合图示步骤,让用户能够一步步理解。可能需要分步骤,先讲暴力匹配的问题,引出KMP的必要性,再讲next数组的构建,最后整个匹配过程,并配以例子说明。</think>好的!我用一个缝补衣服的比喻帮你理解KMP算法,配合图文说明,包你10分钟彻底掌握这个算法。 --- ### 一、先看生活场景 假设你正在缝补衣服(主串),针线(模式串)需要精准穿过破洞的位置。暴力匹配法像新手缝补——每次针扎错位置就把线全部抽回重试(图1)。而KMP算法像老裁缝——发现错误时,线只需回退到特定位置继续缝补(图2)。 ![暴力匹配VS KMP](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8f0e4a5f5f2c4c3d9c5e5d8d4a0a2a0d~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=600&h=300&s=78965&e=png&b=fefdfd) --- ### 二、核心思想 **记住这句话:** 当出现不匹配时,模式串不是回退到起点,而是根据已匹配部分的信息,滑动到合理位置继续匹配。 --- ### 三、关键工具:Next数组 我们给模式串做个"身份证"——Next数组(部分匹配表)。以模式串`ABABC`为例: | 位置 | 0 | 1 | 2 | 3 | 4 | |------|---|---|---|---|---| | 字符 | A | B | A | B | C | | Next | 0 | 0 | 1 | 2 | 0 | **计算规则:** 1. 每个位置记录的是**当前字符之前**的子串中,最长相同前后缀的长度 2. 前缀:不包含最后一个字符的所有子串 3. 后缀:不包含第一个字符的所有子串 **举个栗子🌰:** 位置3(字符B)之前的子串是`ABA`,最长相同前后缀是`A`,所以Next[3]=1 --- ### 四、匹配过程演示 主串:ABABABABC 模式串:ABABC **步骤分解:** ``` 主串索引:0 1 2 3 4 5 6 7 8 主串字符:A B A B A B A B C 模式串: A B A B C ↑ ↑ ↑ ↑ ✖ 0 1 2 3 4 ``` 1. 前4个字符匹配成功,第4位主串A vs 模式串C不匹配 2. 查Next数组:Next[3]=2 3. 模式串滑动到索引2的位置继续匹配: ``` 主串索引:...4 5 6 7 8 主串字符:...A B A B C 模式串: A B A B C ↑ ↑ ↑ ↑ ↑ 0 1 2 3 4 ``` 此时完全匹配成功! --- ### 五、Next数组生成口诀 记住这个三步口诀: 1. 初始化:Next[0]=0,i=1,j=0 2. 前后缀相同:Next[i]=j+1,i和j都+1 3. 前后缀不同:j回退到Next[j-1],直到j=0 **生成过程动态演示(以ABABC为例):** ![Next数组生成](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3d2f1a5c4c9c4d5e9a3b5c5d5d9d5b5d~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=600&h=300&s=123456&e=png&b=fefefe) --- ### 六、时间复杂度对比 | 算法 | 时间复杂度 | 特点 | |------------|------------|------------------------| | 暴力匹配 | O(mn) | 简单但效率低 | | KMP算法 | O(m+n) | 预处理Next数组后效率高 | --- ### 七、常见应用场景 1. 文本编辑器中的查找替换功能 2. 病毒特征码扫描 3. DNA序列匹配 4. 论文查重系统 --- **现在考考你:** 如果模式串是`ABCDABD`,它的Next数组应该是什么?试试用刚才的口诀计算吧!(答案:`[0,0,0,0,1,2,0]`)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值