kmp疑点讲解

 本文适合明白kmp原理,但还有疑惑的朋友看,故而KMP原理不作讲解。kmp原理请自行百度。

kmp的常见疑惑:

  1.  为什么移动模式串的过程中不会有遗漏?
  2. 为什么要用最大相同前后缀?
  3. next数组到底是什么,怎么求? 
  4. 理解原理,代码看不懂,不会写,怎么办?

1. 为什么移动模式串时不会有遗漏?

--------为完全匹配的任意字符

AB为最大相同前后缀,如果不了解什么是最大相同前后缀,请下滑先看3.关于前后缀的介绍

首先我们在下标9处失配

那么按照kmp 算法我们会移动子串至最大相同前后缀AB处

那么假设现在,我们有遗漏,在下图处恰好匹配

那么最大的相同前后缀应该是,红色框内的

但是前面已经说过了,最大相同前后缀是 AB,那么

 

这种情况与最大相同前后缀是 AB相冲突,所以不可能出现。

如果在移动过程中,出现了恰好完全匹配的情况,那么只能说明,我们找的最大相同前后缀是错的,并不是最大的。

2.为什么要用最大相同前后缀?

其实看到此处,且认真看懂了1.的话,应该已经明白了,使用最大相同前后缀,就是为了,避免移动模式串的过程中出现遗漏的情况。

3.next数组到底是什么,怎么求?

next数组只是一个工具,根据不同的定义,会得到不同的值。

常见的 是以-1 ,0 开头,或者以0 ,1开头的,考研用的也是这两种,但是我没有了解过这两种,而接下来我要介绍的是一种完全不同的next数组的定义。

next数组的值是代表着字符串的前缀与后缀相同的最大长度,即最大相同前后缀的长度,可能这句话现在让你无法理解,请耐心看下去。

介绍next数组就不可避免需要理清楚前后缀的脉络。

1.何为前后缀

那么假定模式串为“01010” 

前缀不可包含最后一个字符,后缀不可包含第一个字符。

“0”                 这个串既无前缀也无后缀,因为0既是第一个也是最后一个字符。  故next[0]=0

“01”                前缀:0        后缀:1         0和1没有相同处。故而next[1]=0

“010”              前缀:01        后缀:10        01和10,0相同,0为最大相同前后缀,故next[2]=1

“0101”            前缀:010        后缀:101        01和01相同,01为最大相同前后缀故next[3]=2

“01010”           前缀:0101      后缀:1010     010和010相同,010为最大相同前后缀故next[4]=3

2.那么如何求取next数组呢?

讲解未动,代码先行,看不懂没关系,继续往下看。

void GetNext(const string &s ,int* const next)
{
    next[0] = 0;
    int len = s.length();
    for (int i = 1, j = 0;i < len;i++)
    {
        while (j > 0 && s[i] != s[j])
        {
            j = next[j - 1];
        }
        if (s[i] == s[j])
        {
            j++;
        }
        next[i] = j;
    }
}

代码大家应该是看不懂的,耐心看了讲解回头看代码就好。

 假如黄色框内和绿色框内的第一个待匹配的字符串相同,由于黑色框内是长度j的最大相同前后缀,那么 next[ 黄色框第一个字符处 ] =j+1,假如黄色框内和绿色框内的第二个待匹配的字符串相同

 ,那么next[ 黄色框第二个字符处 ] =j+2;

 上述是关于相等时的情况,那么假如s[i]!=s[j]呢?

重新举一个例子:

 按照代码

 当不相等时:j=next[j-1] ,即如下图:

 好了,到此为止,大家应该已经明白了next数组的求法。

4.理解原理,代码看不懂,不会写,怎么办?

讲解未动,代码先行,看不懂没关系,继续往下看。:

/****************************************************

    > File Name:     KMP
    > Author:        唯恒
    > Mail:          2279811789@qq.com
    > Created Time:  2022年10月22日 

*******************************************************/

#include <iostream>
using namespace std;

void GetNext(const string &s ,int* const next)
{
    next[0] = 0;
    int len = s.length();
    for (int i = 1, j = 0;i < len;i++)
    {
        while (j > 0 && s[i] != s[j])
        {
            j = next[j - 1];
        }
        if (s[i] == s[j])
        {
            j++;
        }
        next[i] = j;
    }
}

int Kmp(const string &s,const string &t)
{
    int* const next = new int[t.length()];
    GetNext(t, next);

    for (int i = 1, j = 0;i < s.length();i++)
    {
        while (j > 0 && s[i] != t[j])
        {
            j = next[j - 1];
        }
        if (s[i] == t[j])
        {
            j++;
        }
        if (j == t.length())
        {
            return i - j + 1;
        }
    }
    return -1;
}
int main()
{
   string s = "ABCDDDABA";
    string t0 = "ABA";
    int i= Kmp(s, t0);
    if (  i!=-1)
    {
        cout << Kmp(s, t0) << endl;
    }
    else
    {
        cout << "主串中无子串" << endl;
    }
    string t1 = "ABc";
     i = Kmp(s, t1);
    if ( i != -1)
    {
        cout << Kmp(s, t1) << endl;
    }
    else
    {
        cout << "主串中无子串" << endl;
    }
   
    return 0;
}


//111,0,111,1

首先i是主串的下标,kmp主串指针不会后移,故而在在for里i++,每一轮都要增加。

其次如果s[i]和t[j]相等那么移动模式串指针,让模式串下一个字符和主串下一个字符比较。

当j==模式串长度时,说明模式串已经比较到末尾了。返回i-j+1,因为返回的是模式串在主串中第一次出现的位置,所以需要减去模式串的长度,+1是因为人们习惯于从1开始计数,如果像数组一样从0开始,不+1也可以。

接下来: 

 当s[i]!=t[j]时让j=next[j-1],即j=当前最大相同最大前后缀的长度。

举个例子吧

当0和1失配,那么0这个next[2]的最大相同前后缀为0,将j=0,下一次就差t[0]开始比较

假如一直找不到相等的,那么一直循环,当t[0]时还是!=的话,那么,因为

退出循环,主串移动指针 ,让下一个指针和模式串的t[0]比较。

 最后

 

当for结束还是没有找出的话,说明主串里找不到模式串,返回-1。

结语:

看不懂没关系,反复看就行,我也是看了很久才明白的,带入数据进程序手动运算,想想自己对kmp算法的原理是否彻底搞懂,再结合别的博客,b站上的视频理解。

姑妄言之,如是我闻,不喜勿喷。

大道唯恒,事注乃成;希翼列位斧正,愿同诸君共勉。

<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原理对优化特殊场景的字符串处理至关重要。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值