当前位置: 首页 > news >正文

KMP 算法全解析:高效实现字符串匹配与模板题详解

文章目录

      • KMP算法模板
      • 洛谷P3375【模板】KMP例题
        • 算法思路
        • 代码示例

在做KMP算法处理字符串类型题前,可以先参考一下KMP算法原理

下面是笔者写的一个简单KMP算法示例~

在这里插入图片描述

KMP算法模板

#include <iostream>
#include <vector>
using namespace std;

// 生成 nextval 数组
// nextval 数组是对 next 数组的优化,用于在 KMP 算法中更高效地进行字符串匹配
// 它可以避免在匹配过程中进行不必要的回溯
vector<int> getNextval(const string& P) {
    // 获取模式串 P 的长度
    int n = P.size();
    // 初始化 nextval 数组,长度为 n,初始值都设为 0
    vector<int> nextval(n, 0);
    // nextval 数组的第一个元素设为 -1,表示当模式串的第一个字符就不匹配时,主串指针需要后移
    nextval[0] = -1;
    // j 用于遍历模式串 P,k 用于记录当前最长相等前后缀的长度
    int j = 0, k = -1;
    // 开始构建 nextval 数组,循环直到 j 遍历到模式串的倒数第二个字符
    while (j < n - 1) {
        // 如果 k 为 -1 或者当前遍历到的模式串字符 P[j] 等于 P[k]
        if (k == -1 || P[j] == P[k]) {
            // j 和 k 都向后移动一位
            j++; k++;
            // 如果 P[j] 不等于 P[k],则 nextval[j] 就等于 k
            // 否则,将 nextval[k] 的值赋给 nextval[j],避免不必要的回溯
            nextval[j] = (P[j] != P[k]) ? k : nextval[k];
        } else {
            // 如果 P[j] 不等于 P[k],则将 k 更新为 nextval[k],继续寻找合适的前后缀
            k = nextval[k];
        }
    }
    // 返回构建好的 nextval 数组
    return nextval;
}

// KMP 匹配算法
// 在主串 S 中查找模式串 P 第一次出现的位置
int kmp(const string& S, const string& P) {
    // 调用 getNextval 函数生成模式串 P 的 nextval 数组
    vector<int> nextval = getNextval(P);
    // i 用于遍历主串 S,j 用于遍历模式串 P
    int i = 0, j = 0;
    // 获取主串 S 和模式串 P 的长度
    int lenS = S.size(), lenP = P.size();
    // 开始进行匹配,只要主串和模式串都还没遍历完就继续循环
    while (i < lenS && j < lenP) {
        // 如果 j 为 -1 或者当前主串字符 S[i] 等于模式串字符 P[j]
        if (j == -1 || S[i] == P[j]) {
            // 主串和模式串的指针都向后移动一位
            i++; j++;
        } else {
            // 如果不匹配,则将 j 更新为 nextval[j],根据 nextval 数组进行回溯
            j = nextval[j];
        }
    }
    // 如果模式串 P 被完全匹配(j 等于模式串的长度),则返回匹配的起始位置
    // 匹配起始位置为 i - j,否则返回 -1 表示未找到匹配
    return (j == lenP) ? i - j : -1;
}

int main() {
    // 定义主串 S
    string S = "ababaaababaaababaaababaa";
    // 定义模式串 P
    string P = "ababaaababaa";
    // 调用 kmp 函数在主串 S 中查找模式串 P 的位置
    int pos = kmp(S, P);
    // 输出匹配的位置
    cout << "匹配位置: " << pos << endl; // 输出 0
    return 0;
}

洛谷P3375【模板】KMP例题

在这里插入图片描述
输入样例

ABABABC
ABA

输出样例

1
3
0 0 1 
算法思路

本题主要涉及两个核心任务:一是找出字符串 s2 在字符串 s1 中所有出现的位置;二是计算 s2 每个前缀的最长 border 长度。我们可以使用 KMP(Knuth-Morris-Pratt)算法来高效地完成这两个任务。

  1. 计算最长 border 长度(构建 next 数组)
  • next 数组的定义:next[i] 表示字符串 s2 长度为 i 的前缀的最长 border 长度。
  • 构建过程:使用双指针法,一个指针 i 用于遍历字符串 s2,另一个指针 j 用于记录当前最长 border 的长度。当 s2[i]=s2[j]时,ij 同时后移;当 s2[i]!=s2[j] 时,j 回溯到 next[j - 1] 的位置继续比较,直到找到匹配或 j 为 0。
  1. 字符串匹配
  • 利用已经构建好的 next 数组,使用双指针法s1 中匹配 s2。一个指针 i 遍历 s1,另一个指针 j 遍历 s2。当 s1[i]=s2[j] 时,ij 同时后移;当 s1[i]!=s2[j] 时,j 回溯到 next[j - 1] 的位置继续比较,直到找到匹配或 j 为 0。当 j 等于 s2 的长度时,说明找到了一个匹配,记录匹配位置并继续匹配。
代码示例
#include <iostream>
#include <vector>
#include <string>

using namespace std;

// 构建 next 数组
vector<int> getNext(const string& s) {
    int n = s.size();
    vector<int> next(n, 0);
    for (int i = 1, j = 0; i < n; ++i) {
        while (j > 0 && s[i] != s[j]) {
            j = next[j - 1];
        }
        if (s[i] == s[j]) {
            ++j;
        }
        next[i] = j;
    }
    return next;
}

// KMP 匹配
vector<int> kmp(const string& s1, const string& s2, const vector<int>& next) {
    vector<int> positions;
    int n1 = s1.size(), n2 = s2.size();
    for (int i = 0, j = 0; i < n1; ++i) {
        while (j > 0 && s1[i] != s2[j]) {
            j = next[j - 1];
        }
        if (s1[i] == s2[j]) {
            ++j;
        }
        if (j == n2) {
            positions.push_back(i - n2 + 1 + 1); // 位置从 1 开始计数
            j = next[j - 1];
        }
    }
    return positions;
}

// 输出匹配位置
void printPositions(const vector<int>& positions) {
    for (int pos : positions) {
        cout << pos << endl;
    }
}

// 输出每个前缀的最长 border 长度
void printNext(const vector<int>& next) {
    for (int i = 0; i < next.size(); ++i) {
        if (i > 0) cout << " ";
        cout << next[i];
    }
    cout << endl;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    string s1, s2;
    cin >> s1 >> s2;

    // 构建 next 数组
    vector<int> next = getNext(s2);

    // 进行 KMP 匹配
    vector<int> positions = kmp(s1, s2, next);

    // 输出匹配位置
    printPositions(positions);

    // 输出每个前缀的最长 border 长度
    printNext(next);

    return 0;
}
http://www.dtcms.com/a/111983.html

相关文章:

  • golang 的encoding/json包
  • python爬虫:小程序逆向(需要的工具前期准备)
  • 蓝桥杯---最短路径问题bfs
  • 【数据结构】邻接表 vs 邻接矩阵:5大核心优势解析与稀疏图存储优化指南
  • 三部门新政力推智能家居 居然智家数智化转型迎利好东风
  • YUV转RGBA的操作
  • 图解AUTOSAR_SWS_LINTransceiverDriver
  • 【嵌入式学习5】PyQt5模块介绍、创建第一个窗口
  • Linux安装启动ssh服务器以及ssh的配置
  • Leetcode 15 -- 双指针
  • Mysql 中的 MyISAM 引擎
  • 第十五届蓝桥杯单片机省赛程序设计试题
  • CSS 3D变换,transform:translateZ()
  • 从小米汽车事故反思 LabVIEW 开发
  • 专业的情商测评工具:EQ-i在线测评系统
  • Fastjson 处理 JSON 生成与解析指南
  • 31--当认证协议开始“选秀“:RADIUS、LDAP、AD与本地认证的C位之争
  • react redux的学习,多个reducer
  • drawio导出流程图为白色背景png图片
  • 对OSPF协议的LSA分析
  • Linux系统进程
  • AI 浪潮下企业身份管理:特点凸显,安全挑战升级
  • CMake学习-- install 指令详细说明
  • 11.多线程-信号量-线程池
  • AWS 云运维管理指南
  • ekf-imu --- 四元数乘法符号 ⊗ 的含义
  • SQLite 触发器
  • 深入解析CPU主要参数:选购与性能评估指南
  • ngx_alloc
  • 【2022】【论文笔记】基于相变材料的光学激活的、用于THz光束操作的编码超表面——