金华市网站建设_网站建设公司_Redis_seo优化
2026/1/13 7:03:50 网站建设 项目流程

信奥赛C++提高组csp-s之KMP算法详解

一、KMP算法概述

KMP算法(Knuth-Morris-Pratt算法)是一种高效的字符串匹配算法,用于在文本串中查找模式串的出现位置。与朴素的暴力匹配相比,KMP算法的时间复杂度为O(n+m),其中n是文本串长度,m是模式串长度。

二、核心思想:利用已匹配信息避免重复比较
朴素算法的缺点
// 暴力匹配示例for(inti=0;i<=n-m;i++){boolfound=true;for(intj=0;j<m;j++){if(text[i+j]!=pattern[j]){found=false;break;}}if(found){// 找到匹配}}// 时间复杂度:O(n*m)

KMP算法的核心是:当出现不匹配时,利用已知信息,将模式串向右滑动尽可能远的距离,而不是每次只移动一位

三、关键概念:前缀函数(next数组)
1. 什么是前缀函数?
  • 对于模式串P的每个位置i(1≤ i ≤ m),定义前缀函数nxt[i]表示:
    • 子串P[1:i]的最长相同真前缀和真后缀的长度
    • 真前缀:不包括字符串本身的前缀
    • 真后缀:不包括字符串本身的后缀
2. next数组的作用
  • 记录匹配失败时,模式串应该回退到哪个位置继续匹配
  • 避免主串指针回退,保持O(n)的时间复杂度
四、next数组的构造(重点!)
1. 理解前缀和后缀

对于字符串"ababa":

  • 真前缀有:“a”, “ab”, “aba”, “abab”
  • 真后缀有:“a”, “ba”, “aba”, “baba”

最长公共前后缀:“aba”,长度为3

2. next数组的手动计算过程

以模式串"ABABCABAA"为例(备注:举例中的字符串下标从1开始):

步骤分解:

模式串:A B A B C A B A A 索引: 1 2 3 4 5 6 7 8 9 next: 0 0 1 2 0 1 2 3 1

计算过程详解:

  1. i=1: 子串"A" → 没有真前缀和真后缀 → next[1]=0
  2. i=2: 子串"AB" → 前缀{“A”},后缀{“B”} → 无公共 → next[2]=0
  3. i=3: 子串"ABA" → 前缀{“A”,“AB”},后缀{“A”,“BA”} → 最长公共"A" → next[3]=1
  4. i=4: 子串"ABAB" → 前缀{“A”,“AB”,“ABA”},后缀{“B”,“AB”,“BAB”} → 最长公共"AB" → next[4]=2
  5. i=5: 子串"ABABC" → 无公共前后缀 → next[5]=0
  6. i=6: 子串"ABABCA" → 最长公共"A" → next[6]=1
  7. i=7: 子串"ABABCAB" → 最长公共"AB" → next[7]=2
  8. i=8: 子串"ABABCABA" → 最长公共"ABA" → next[8]=3
  9. i=9: 子串"ABABCABAA" → 最长公共"A" → next[9]=1
3.next数组的构造算法思想

构造next数组的过程本质上是模式串的自我匹配

  1. 初始化:next[1] = 0,设j = 0(j表示当前已匹配的前缀长度)
  2. 递推计算:对于i从1到m-1(m为模式串长度)
    • 如果p[i+1] == p[j+1],则j++,next[i+1] = j
    • 否则,将j回退到next[j],继续比较
    • 如果j回退到0仍不匹配,则next[i] = 0

关键理解

  • j实际上表示当前位置之前的最长匹配前缀的长度

  • 当p[i+1] != p[j+1]时,我们不是从头开始,而是利用已经计算好的next[j]来回退

  • 我们可以把next数组看作一个状态转移函数

    • 当前状态为j(已匹配的长度)
    • 如果下一个字符匹配成功,转移到状态j+1
    • 如果下一个字符匹配失败,转移到状态next[j]

4. next数组的代码实现
nxt[0]=0;// 边界条件,空字符串的border长度为0nxt[1]=0;// 单个字符没有非自身的border,长度为0// 计算nxt[2]到nxt[len_p]for(inti=1,j=0;i<len_p;i++){// i从1开始,实际计算的是nxt[i+1],j表示当前已匹配的前缀长度// 当j>0且下一个字符不匹配时,利用已知的next信息回溯jwhile(j>0&&p[i+1]!=p[j+1]){j=nxt[j];// 回溯到前一个可能匹配的位置}// 如果下一个字符匹配,则增加匹配长度if(p[i+1]==p[j+1]){j++;}// 记录当前前缀的最长border长度nxt[i+1]=j;// nxt[i+1] = 前缀p[1...i+1]的最长border长度}
五、KMP匹配过程详解
示例:在文本串中查找模式串(备注:举例中的字符串S和P的下标从1开始)
文本串s: "ABABDABACDABABCABAB" 模式串p: "ABABCABAB" next数组: [0, 0, 1, 2, 0, 1, 2, 3, 4] 1 2 3 4 5 6 7 8 9 (模式串索引)
匹配过程:
  1. 首次失配(位置5)

    文本串:ABAB D ... 模式串:ABAB C ... 已匹配:ABAB (j=4) 失配字符:D vs C
    • 执行j = next[4] = 2
    • 模式串滑动:比较文本串的D与模式串的p[3]=A
    • 继续失配,j = next[2] = 0
    • 最终从模式串开头重新比较
  2. 完全匹配(位置11-19)

    文本串位置:11 12 13 14 15 16 17 18 19 文本串字符:A B A B C A B A B 模式串字符:A B A B C A B A B 匹配过程:逐步匹配,j从0增加到9
    • 匹配成功后输出位置:i - len(p) + 2 = 18 - 9 + 2 = 11
    • 继续:j = next[9] = 4,准备可能的重叠匹
KMP匹配的代码实现:
for(inti=0,j=0;i<len_s;i++){// i遍历文本串,j表示当前匹配的模式串长度// 当字符不匹配且j>0时,利用next数组回溯j,避免文本串指针i回退while(j>0&&s[i+1]!=p[j+1]){j=nxt[j];// 模式串向右滑动}// 当前字符匹配,模式串匹配长度加1if(s[i+1]==p[j+1]){j++;}// 如果完全匹配模式串if(j==len_p){// 输出匹配位置:当前文本串位置i+1减去模式串长度再加1// 因为字符串从1开始索引,所以起始位置为 i - len_p + 2printf("%d\n",i-len_p+2);// 继续寻找下一个匹配:将j设为当前匹配串的最长border长度j=nxt[j];}}

研究案例

题目描述

给出两个字符串s 1 s_1s1s 2 s_2s2,若s 1 s_1s1的区间[ l , r ] [l, r][l,r]子串与s 2 s_2s2完全相同,则称s 2 s_2s2s 1 s_1s1中出现了,其出现位置为l ll
现在请你求出s 2 s_2s2s 1 s_1s1中所有出现的位置。

定义一个字符串s ss的 border 为s ss的一个s ss本身的子串t tt,满足t tt既是s ss的前缀,又是s ss的后缀。
对于s 2 s_2s2,你还需要求出对于其每个前缀s ′ s's的最长 bordert ′ t't的长度。

输入格式

第一行为一个字符串,即为s 1 s_1s1
第二行为一个字符串,即为s 2 s_2s2

输出格式

首先输出若干行,每行一个整数,按从小到大的顺序输出s 2 s_2s2s 1 s_1s1中出现的位置。
最后一行输出∣ s 2 ∣ |s_2|s2个整数,第i ii个整数表示s 2 s_2s2的长度为i ii的前缀的最长 border 长度。

输入 #1
ABABABC ABA
输出 #1
1 3 0 0 1
说明/提示
样例 1 解释

对于s 2 s_2s2长度为3 33的前缀ABA,字符串A既是其后缀也是其前缀,且是最长的,因此最长 border 长度为1 11

数据规模与约定

本题采用多测试点捆绑测试,共有 4 个子任务

  • Subtask 0(30 points):∣ s 1 ∣ ≤ 15 |s_1| \leq 15s115∣ s 2 ∣ ≤ 5 |s_2| \leq 5s25
  • Subtask 1(40 points):∣ s 1 ∣ ≤ 10 4 |s_1| \leq 10^4s1104∣ s 2 ∣ ≤ 10 2 |s_2| \leq 10^2s2102
  • Subtask 2(30 points):无特殊约定。
  • Subtask 3(0 points):Hack。

对于全部的测试点,保证1 ≤ ∣ s 1 ∣ , ∣ s 2 ∣ ≤ 10 6 1 \leq |s_1|,|s_2| \leq 10^61s1,s2106s 1 , s 2 s_1, s_2s1,s2中均只含大写英文字母。

代码实现

#include<bits/stdc++.h>usingnamespacestd;constintN=1e6+10;// 定义足够大的数组大小,满足题目数据范围chars[N],p[N];// s为文本串,p为模式串,下标从1开始intnxt[N];// next数组,nxt[i]表示模式串前i个字符组成的前缀的最长border长度intmain(){// 输入字符串,从下标1开始存储scanf("%s%s",s+1,p+1);intlen_s=strlen(s+1);// 文本串长度intlen_p=strlen(p+1);// 模式串长度// ---------- 第一部分:计算模式串的next数组(前缀函数) ----------nxt[0]=0;// 边界条件,空字符串的border长度为0nxt[1]=0;// 单个字符没有非自身的border,长度为0// 计算nxt[2]到nxt[len_p]for(inti=1,j=0;i<len_p;i++){// i从1到len_p-1,实际计算的是nxt[i+1]// 当j>0且下一个字符不匹配时,利用已知的next信息回溯jwhile(j>0&&p[i+1]!=p[j+1]){j=nxt[j];// 回溯到前一个可能匹配的位置}// 如果下一个字符匹配,则增加匹配长度if(p[i+1]==p[j+1]){j++;}// 记录当前前缀的最长border长度nxt[i+1]=j;// nxt[i+1] = 前缀p[1...i+1]的最长border长度}// ---------- 第二部分:KMP匹配过程 ----------for(inti=0,j=0;i<len_s;i++){// i遍历文本串,j表示当前匹配的模式串长度// 当字符不匹配且j>0时,利用next数组回溯j,避免文本串指针i回退while(j>0&&s[i+1]!=p[j+1]){j=nxt[j];// 模式串向右滑动}// 当前字符匹配,模式串匹配长度加1if(s[i+1]==p[j+1]){j++;}// 如果完全匹配模式串if(j==len_p){// 输出匹配位置:当前文本串位置i+1减去模式串长度再加1// 因为字符串从1开始索引,所以起始位置为 i - len_p + 2printf("%d\n",i-len_p+2);// 继续寻找下一个匹配:将j设为当前匹配串的最长border长度j=nxt[j];}}// ---------- 第三部分:输出模式串每个前缀的最长border长度 ----------for(inti=1;i<=len_p;i++){printf("%d ",nxt[i]);}return0;}

功能分析

1.算法目标
  • 实现KMP字符串匹配算法,解决两个问题:
    a) 找出模式串在文本串中所有出现的位置
    b) 计算模式串每个前缀的最长border长度
2.核心数据结构
  • nxt[]数组:存储模式串的“前缀函数”值,nxt[i]表示子串p[1...i]的最长border长度
    • 定义:border是字符串的非本身的前缀,且同时是该字符串的后缀
    • 例如:"ABA"的最长border是"A",长度为1
3.算法流程
第一步:预处理模式串(计算next数组)
  • 目的:得到模式串的“自匹配”信息,用于匹配失败时快速跳过不必要的比较
  • 关键操作
    • 初始化nxt[0]=0, nxt[1]=0
    • 使用双指针ij
      • i:当前正在计算的前缀的结束位置(实际代码中i从1开始,计算nxt[i+1]
      • j:当前已匹配的前缀长度,也指向下一个待比较字符
    • 循环中不断尝试扩展匹配长度,失败时通过j = nxt[j]回溯到更短的匹配前缀
  • 时间复杂度:O(len_p)
第二步:文本串匹配
  • 目的:在文本串中找到所有模式串出现的位置
  • 关键操作
    • 双指针i(文本串指针)和j(模式串匹配长度)
    • 不匹配时,j通过nxt数组回溯,i永不回退
    • 匹配成功时输出位置,并继续通过nxt[j]寻找下一个可能的匹配
  • 时间复杂度:O(len_s)
4.算法特点
  • 时间复杂度:O(len_s + len_p),线性复杂度
  • 空间复杂度:O(len_p),用于存储next数组
  • 核心优势:通过next数组避免文本串指针回退,显著提高效率
5.代码细节说明
  • 字符串存储:从下标1开始存储,方便与next数组下标对齐
  • 边界处理
    • 模式串长度为1时,next数组计算循环不执行,直接使用初始值
    • 匹配成功后执行j = nxt[j],确保不遗漏重叠匹配(如文本串"AAAA"中找模式串"AA"
  • 位置计算:匹配位置计算公式为i - len_p + 2
    • i:当前循环变量(从0开始)
    • len_p:模式串长度
    • +2:补偿下标从1开始和匹配结束位置到起始位置的转换
6.示例验证

对于输入:

ABABABC ABA

程序执行:

  • 计算next数组:nxt[1]=0, nxt[2]=0, nxt[3]=1

  • 匹配过程:

    • 位置1匹配成功(ABA
    • 位置3匹配成功(ABA
  • 输出:

    1 3 0 0 1

更多系列知识,请查看专栏:《信奥赛C++提高组csp-s知识详解及案例实践》:
https://blog.csdn.net/weixin_66461496/category_13113932.html

各种学习资料,助力大家一站式学习和提升!!!

#include<bits/stdc++.h>usingnamespacestd;intmain(){cout<<"########## 一站式掌握信奥赛知识! ##########";cout<<"############# 冲刺信奥赛拿奖! #############";cout<<"###### 课程购买后永久学习,不受限制! ######";return0;}
  • 一、CSP信奥赛C++通关学习视频课:
    • C++语法基础
    • C++语法进阶
    • C++算法
    • C++数据结构
    • CSP信奥赛数学
    • CSP信奥赛STL
  • 二、CSP信奥赛C++竞赛拿奖视频课:
    • 信奥赛csp-j初赛高频考点解析
    • CSP信奥赛C++复赛集训课(12大高频考点专题集训)
  • 三、csp高频考点知识详解及案例实践:
    • CSP信奥赛C++之动态规划
    • CSP信奥赛C++之标准模板库STL
    • 信奥赛C++提高组csp-s知识详解及案例实践
  • 四、考级、竞赛刷题题单及题解:
    • GESP C++考级真题题解
    • CSP信奥赛C++初赛及复赛高频考点真题解析
    • CSP信奥赛C++一等奖通关刷题题单及题解

详细内容:

1、csp/信奥赛C++,完整信奥赛系列课程(永久学习):

https://edu.csdn.net/lecturer/7901 点击跳转


2、CSP信奥赛C++竞赛拿奖视频课:

https://edu.csdn.net/course/detail/40437 点击跳转

3、csp信奥赛高频考点知识详解及案例实践:

CSP信奥赛C++动态规划:
https://blog.csdn.net/weixin_66461496/category_13096895.html点击跳转

CSP信奥赛C++标准模板库STL:
https://blog.csdn.net/weixin_66461496/category_13108077.html 点击跳转

信奥赛C++提高组csp-s知识详解及案例实践:
https://blog.csdn.net/weixin_66461496/category_13113932.html

4、csp信奥赛冲刺一等奖有效刷题题解:

CSP信奥赛C++初赛及复赛高频考点真题解析(持续更新):https://blog.csdn.net/weixin_66461496/category_12808781.html 点击跳转

CSP信奥赛C++一等奖通关刷题题单及题解(持续更新):https://blog.csdn.net/weixin_66461496/category_12673810.html 点击跳转

5、GESP C++考级真题题解:

GESP(C++ 一级+二级+三级)真题题解(持续更新):https://blog.csdn.net/weixin_66461496/category_12858102.html 点击跳转

GESP(C++ 四级+五级+六级)真题题解(持续更新):https://blog.csdn.net/weixin_66461496/category_12869848.html 点击跳转

· 文末祝福 ·

#include<bits/stdc++.h>usingnamespacestd;intmain(){cout<<"跟着王老师一起学习信奥赛C++";cout<<" 成就更好的自己! ";cout<<" csp信奥赛一等奖属于你! ";return0;}

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询