KMP算法详细讲解

文章篇幅有点长,但是最后一张图真的很棒,希望读者坚持慢慢看到最后,必有收获。

字符串单模式问题描述:

给定文本串text,和模式串pattern,在文本串text中找到模式串pattern第一次出现的位置。


一、最基本的字符串单模式匹配算法:暴力求解(Brute Force):时间复杂度O(m*n)

设文本串text = "ababcabcacbab",模式串为patten = "abcac"     其匹配过程如下图所示。

黑色线条代表匹配位置,红色斜杠代表失配位置。






可以看到每次失配之后,模式串都向右移动一位在去从第一个字符开始于文本串匹配。并且

在已经知道很多字符都配不上的情况下,还要这样移动着去配,是非常浪费时间的。

BF算法:

int Brute_ForceSearch(string t,string p)
{
    int i=0,j=0;
    int len = t.length(),len_p = p.length();
    while((i < len)&&(j<len_p)){
        if(t[i+j]==p[j])///若匹配则模式串后移
            j++;
        else{///不匹配,则比较下一个位置,模式串回到0位
            i++;
            j = 0;
        }
    } 
    if(j >= len_p)
        return i;
    return -1;
}


二、KMP算法的本质。

在BF中,假如从文本串的第i个字符来开始于模式串匹配。当匹配到模式串的第j位发现失配

即text[i+j] != patten[j]的时候,我们又从文本串的第i+1个位置来重新开始匹配。尽管我们已经

知道了好多字符其实根本就匹配不上,我们还是进行了这个过程,这个时候回溯的过程会非常

耗费我们的时间。而KMP算法的实质就是,当遇到text[i+j] != patten[j]的时候,但是我们知道

模式串中的 0~j-1 位置上的字符已经于i ~ i+j-1位置上的字符是完全匹配的。这样我们可以在0~j-1

中找到一个前缀A和后缀B相等并且最长的那个串,然后将A移动到B的位置再开始重新匹配即可。

这样就减少了一些不必要的匹配。时间复杂度O(n)

Next数组的求法:

普及两个概念:

前缀和后缀:例如一个字符串:abcd

它的前缀有                         它的后缀有

a                                                      d

ab                                                  cd

abc                                               bcd

我们这里所说的前缀、后缀不包括字符串自身。

求next数组的时候,对于模式串的位置j,考察patten[j-1].查找字符串patten[j-1]的最大相等的前缀和后缀。

假设最大相等前缀和后缀长度为k,则有k使得  p[0]p[1]p[2]......p[k-2]p[k-1] = p[j-k]p[j-k+1]......p[j-2]p[j-1]。

例如模式串Patten = "abaabcac"。其next数组如图所示:


我们可以看图中第一个c字符的下标是5,其next数组的值是2.也就是说,模式串里面当配到c这个

字符失配的时候,再文本串中,abaab都是已经配好的,我们发现patten前面出现过ab,所以我们从

之前的ab串的后一个字符继续匹配就行了。如下图所示:


从图中我们可以看出蓝色位置位置失配,蓝色位置前面的字符串中,最长公共前缀后缀是ab,则我们可以直接

把模式串patten向右滑,让黄色位置格子于文本串失配的地方对其。则蓝色格子失配即模式串第6个字符失配,

就从模式串的第三个字符开始配,第三个字符的下标为2,则next[5] = 2;字符串滑过去后如下图所示。


这样我们一下滑过去,就跳过了文本串中的text[1],text[2]。避免了这些不必要的匹配。

现在我们开始来讲求next数组。宏观上我们按下图的步骤求next。

我们用index来表示next数组的下标:


当index = 2时,求ab的最大相等前缀串,后缀串。


当index = 3时,求aba的最大相等前缀串,后缀串。


当index = 4时,对abaa求最大相等前缀串、后缀串。


当j=5时,求abaab的最大相等前缀串,后缀串。


。。。。。。。。。。。。。。。。。。不在往后求了,就是按照这样的方法一直求下去。

看一下代码实现:

/*
付完整代码
*/
#include <iostream>
#include <string.h>
#include <stdio.h>

using namespace std;

const int maxn = 1000010;
char text[maxn];
char patten[maxn];
int next[maxn];
void GetNext()
{
    int len_p = strlen(patten);
    next[0] = -1;
    int k = -1,j = 0 ;
    while(j<len_p-1)
    {
        if(k == -1||patten[j] == patten[k])
        {
            ++k;++j;
            next[j] = k;
        }
        else
            k = next[k];
    }
}
int KMP()
{
    int ans=-1,i = 0,j = 0;
    int len_p = strlen(patten),n=strlen(text);
    while(i<n)
    {
        if(j==-1||text[i] == patten[j])
        {
            ++i;++j;
        }
        else
            j = next[j];
        if(j == len_p){
            return i-len_p;
            break;
        }
    }
    return ans;
}
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%s%s",patten,text);
        GetNext();
        for(int i = 0; i < strlen(patten); i++)
            printf("%d ",next[i]);
        printf("\n");
        printf("%d\n",KMP());
    }
    return 0;
}


代码测试:

当然大家都直到代码该怎么写,也知道上面找前后缀的过程,但是肯定看代码的时候还是会觉得一脸懵。

想当初我也是这样的,理解前后缀,但是就是看不懂代码,所以为了帮助理解代码我又搞了下面这个图。

这时我这篇博客里面最棒的一个图片了,个人认为:



字符串匹配算法还有:Boyer-Moore算法,比KMP算法还快,有兴趣可以自己看。

如有错误请评论告知,必及时改正,以免误认子弟。



<think>嗯,用户想了解KMP算法,并且希望用Python来讲解。首先,我需要回忆一下KMP算法的核心思想。KMP算法主要用于字符串匹配,相比暴力匹配,它通过预处理模式串来避免重复比较,提高效率。关键点在于部分匹配表(也就是前缀函数或者next数组),这一步可能需要详细解释。 接下来,用户可能需要知道为什么需要KMP算法。暴力匹配的时间复杂度是O(mn),而KMP是O(m+n),这在处理大文本时优势明显。所以,我应该先对比一下暴力匹配和KMP的区别,突出其优势。 然后,部分匹配表的构建是关键。这部分可能比较抽象,需要举例说明。例如,对于模式串“ABABC”,如何生成next数组。可能需要分步骤解释每个位置的最长公共前后缀长度,以及如何利用这些信息在匹配失败时跳过不必要的比较。 用户可能对如何将理论转化为Python代码感兴趣。所以,我需要分步骤讲解如何构建next数组,然后如何利用这个数组进行匹配。代码部分需要简洁,同时要有注释,方便理解。另外,要注意处理边界情况,比如模式串为空或者文本比模式串短的情况。 另外,用户可能对KMP的实际应用场景不太清楚,可以举一些例子,比如文本编辑器中的查找功能或者DNA序列匹配等,说明其应用价值。 在解释过程中,要避免使用过于专业的术语,尽量用通俗的语言。同时,可能需要用例子来演示算法的执行过程,比如用具体的文本串和模式串,逐步展示匹配过程,以及next数组如何发挥作用。 还需要检查代码的正确性,确保没有语法错误,并且逻辑正确。比如,构建next数组时,双指针的移动是否正确,匹配过程中索引的处理是否恰当。可能需要用几个测试用例验证代码的正确性,比如模式串出现在文本开头、中间、末尾,或者多次出现的情况。 最后,总结KMP算法的优缺点,以及适用场景,帮助用户全面理解。可能还要指出,虽然KMP算法理论复杂度低,但在实际应用中,如果模式串不长或者文本串不大,暴力匹配可能更简单直接,而KMP在预处理上的开销是否值得需要根据具体情况判断。</think>### KMP算法核心思想 KMP算法(Knuth-Morris-Pratt)是一种**字符串匹配算法**,通过预处理模式串生成`next`数组(部分匹配表),在匹配失败时利用`next`值跳过不必要的字符比较,将时间复杂度优化至$O(m+n)$($m$为模式串长度,$n$为文本串长度)。 --- ### 核心概念:next数组 `next[i]`表示模式串`P[0..i]`中**最长公共前后缀**的长度。 **示例:** 模式串`P = "ABABC"`的`next`数组计算如下: $$ \begin{aligned} P[0] &= "A" &\rightarrow next[0] &= 0 \\ P[0..1] &= "AB" &\rightarrow next[1] &= 0 \\ P[0..2] &= "ABA" &\rightarrow next[2] &= 1 \quad (\text{前缀"A"和后缀"A"}) \\ P[0..3] &= "ABAB" &\rightarrow next[3] &= 2 \quad (\text{前缀"AB"和后缀"AB"}) \\ P[0..4] &= "ABABC" &\rightarrow next[4] &= 0 \end{aligned} $$ --- ### Python实现步骤 #### 1. 构建next数组 ```python def build_next(pattern): next = [0] * len(pattern) j = 0 # 前缀末尾指针 for i in range(1, len(pattern)): # i是后缀末尾指针 while j > 0 and pattern[i] != pattern[j]: j = next[j-1] # 回退到前一个匹配位置 if pattern[i] == pattern[j]: j += 1 next[i] = j return next ``` #### 2. 匹配过程 ```python def kmp_search(text, pattern): if not pattern: return 0 # 空模式直接匹配 next = build_next(pattern) j = 0 # 模式串指针 for i in range(len(text)): while j > 0 and text[i] != pattern[j]: j = next[j-1] # 根据next回退 if text[i] == pattern[j]: j += 1 if j == len(pattern): return i - j + 1 # 返回匹配起始位置 return -1 # 未找到 ``` --- ### 实例演示 **输入:** ```python text = "ABABABCABAABABABCA" pattern = "ABABC" ``` **执行过程:** 1. 生成`next = [0, 0, 1, 2, 0]` 2. 当匹配到`text[4]`时发现`'A' ≠ 'C'` 3. 根据`next[3]=2`回退到`pattern[2]`继续匹配 4. 最终在`text[2:7]`找到完整匹配 --- ### 关键优势 - **跳过无效比较**:通过`next`数组避免回溯文本指针 - **时间复杂度**:预处理$O(m)$ + 匹配$O(n)$,总体$O(m+n)$ - **空间复杂度**:$O(m)$(存储`next`数组) --- ### 应用场景 - 大文本中的关键字搜索 - 生物信息学中的DNA序列匹配 - 代码编辑器/IDE的查找替换功能 实际使用时可直接调用Python标准库`re`模块,但理解KMP原理对优化特殊场景的字符串处理至关重要。
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值