首页 > 编程语言 >KMP算法

KMP算法

时间:2024-08-16 17:51:46浏览次数:18  
标签:匹配 int ne 模式 next 算法 KMP 下标

KMP算法介绍

KMP(Knuth-Morris-Pratt)算法是一种高效的字符串匹配算法,用于在主串(text)中查找模式串(pattern)。KMP通过预处理模式串,避免了在匹配失败时回溯主串,提高了匹配效率。其时间复杂度为 O(n + m),其中 n 是主串的长度,m 是模式串的长度。

KMP算法的关键思想

KMP算法的核心思想是使用部分匹配表(也叫做“前缀函数”或“失配函数”,即 next 数组)来记录模式串中部分匹配的长度。当出现不匹配时,模式串通过 next 数组决定从哪里重新开始匹配,而不是从头开始。

next 数组的作用

next[i] 表示当模式串中的第 i 个字符失配时,应该跳回到模式串的第 next[i] 个字符继续匹配。这个值代表了模式串中已匹配部分的最长相同前缀和后缀的长度。

KMP算法的步骤

  1. 计算 next 数组

    • next[i] 表示模式串 p[0:i] 的前缀和后缀中最长的相同部分的长度。
    • 如果字符匹配,next[i] 增加;如果不匹配,参考之前的部分匹配跳转。
  2. 主串与模式串匹配

    • 在匹配主串时,如果字符不匹配,利用 next 数组来确定模式串应该跳转的位置,避免重新开始匹配。

题目

给定一个字符串 SS,以及一个模式串 PP,所有字符串中只包含大小写英文字母以及阿拉伯数字。

模式串 PP 在字符串 SS 中多次作为子串出现。

求出模式串 PP 在字符串 SS 中所有出现的位置的起始下标。

输入格式

第一行输入整数 NN,表示字符串 PP 的长度。

第二行输入字符串 PP。

第三行输入整数 MM,表示字符串 SS 的长度。

第四行输入字符串 SS。

输出格式

共一行,输出所有出现位置的起始下标(下标从 00 开始计数),整数之间用空格隔开。

数据范围

1≤N≤1051≤N≤105
1≤M≤1061≤M≤106

输入样例:

3
aba
5
ababa

输出样例:

0 2

 感觉一个不错的视频【天勤考研】KMP算法易懂版_哔哩哔哩_bilibili

 模板代码

#include <iostream>

using namespace std;

const int N = 100010, M = 1000010;

int n, m;
int ne[N];
char s[M], p[N];

int main()
{
    cin >> n >> p + 1 >> m >> s + 1;

    for (int i = 2, j = 0; i <= n; i ++ )
    {
        while (j && p[i] != p[j + 1]) j = ne[j];
        if (p[i] == p[j + 1]) j ++ ;
        ne[i] = j;
    }

    for (int i = 1, j = 0; i <= m; i ++ )
    {
        while (j && s[i] != p[j + 1]) j = ne[j];
        if (s[i] == p[j + 1]) j ++ ;
        if (j == n)
        {
            printf("%d ", i - n);
            j = ne[j];
        }
    }

    return 0;
}
//来自y总

逐步解释

初始化变量

int m,n;
//主串长度,模式串长度
int ne[N]
//ne[N]是部分匹配表(也称为“前缀函数”)
int s[M],p[N]
//主串数组,模式串数组

主函数

cin >> n >> p+1 >> m >> s+1;

 为什么是p+1和s+1

  1. 从 1 开始的索引: 在许多竞赛题目或实现中,字符数组的下标往往从 1 开始,而不是从 0 开始。这是因为这样可以简化某些逻辑,比如处理字符串匹配问题时,下标从 1 开始可以减少下标的计算和调整,尤其是在循环或条件判断中。

  2. 输入偏移p + 1s + 1 将指针偏移到数组的第二个位置(即数组的下标为 1 的位置)。因此,当输入字符串时,实际存储的第一个字符会被放在 p[1]s[1] 中,而不是 p[0]s[0]

  3. 好处

    • 避免处理下标为 0 的特殊情况:如果字符串的下标从 1 开始,很多算法中的边界处理会更加简单,避免了处理下标为 0 的特殊情况。
    • 更加贴近竞赛习惯:在许多算法竞赛中,数组或字符串从 1 开始下标是比较常见的习惯,这样的代码风格使得处理问题更加直观。

示例

假设你有如下输入:

3
abc
7
abcabcabc

如果直接使用 ps 作为数组名(不加偏移),p[0] 会存储模式串的第一个字符 'a'p[1] 会存储 'b'p[2] 存储 'c'。但在这段代码中,加了一次偏移,p[1] 就存储了 'a'p[2] 存储 'b'p[3] 存储 'c'。这样,后续处理可以直接从 p[1] 开始,不用处理 p[0] 的边界问题。

部分匹配表的计算(前缀函数),求出ne[N]都为几(公共前后缀长度加一)

for (int i = 2, j = 0; i <= n; i ++ )
//这里我们从 i = 2 开始(注意 p 是从索引 1 开始的,而不是索引 0)。
//j 是模式串的前缀长度,初始值为 0。
//i 遍历模式串 p,范围从 2 到 n,用于计算每个位置的 ne[i]。
    {
        while (j && p[i] != p[j + 1]) j = ne[j];
//j && p[i] != p[j + 1]:
//j 表示当前匹配的前缀长度。如果 j == 0,表示没有可用的前缀,因此循环不会执行。
//如果 p[i] != p[j + 1],表示当前字符不匹配,需要通过 ne[j] 回退到前面可能的匹配位置。
//j = ne[j]:
//当不匹配时,回退 j 到当前最长前缀的长度,即 ne[j]。
        if (p[i] == p[j + 1]) j ++ ;
//如果当前字符 p[i] 和 p[j + 1] 匹配成功,增加 j 的值,表示匹配的前缀长度增加了。
        ne[i] = j;
//最终,ne[i] 被设置为当前的 j 值,即当前 i 位置的最长前缀后缀长度。
//这个值表示模式串 p 中 i 之前的子串中前缀和后缀相等的最大长度。
    }
举例解释

假设模式串 p = "ababca",构建 ne 数组:

  1. i = 2j = 0

    • p[2] = 'b'p[j + 1] = p[1] = 'a',不匹配,j = 0ne[2] = 0
  2. i = 3j = 0

    • p[3] = 'a'p[j + 1] = p[1] = 'a',匹配,j++ne[3] = 1
  3. i = 4j = 1

    • p[4] = 'b'p[j + 1] = p[2] = 'b',匹配,j++ne[4] = 2
  4. i = 5j = 2

    • p[5] = 'c'p[j + 1] = p[3] = 'a',不匹配,j = ne[2] = 0ne[5] = 0
  5. i = 6j = 0

    • p[6] = 'a'p[j + 1] = p[1] = 'a',匹配,j++ne[6] = 1

 

匹配主串与模式串

for (int i = 1, j = 0; i <= m; i ++ )
//个循环遍历文本 s 的每一个字符,从 i = 1 开始,到 i <= m 结束,m 是文本 s 的长度。
//j 表示当前模式串 p 中匹配的字符位置,初始化为 0。
    {
        while (j && s[i] != p[j + 1]) j = ne[j];
//如果当前 s[i] 和 p[j + 1] 不匹配,并且 j > 0,需要回退 j 到模式串的之前位置 ne[j],即当前 j 的//最长可复用前缀的结尾位置,尝试继续匹配。
//j && s[i] != p[j + 1]:
//当 j == 0 时,意味着无法回退,匹配失败,直接退出 while 循环。
//当 s[i] != p[j + 1] 时,表示当前字符不匹配,需要回退到 ne[j] 继续匹配。
//j = ne[j]:
//回退 j 到 ne[j] 处,尝试匹配下一个可能的前缀位置。
        if (s[i] == p[j + 1]) j ++ ;
//如果当前字符 s[i] 和模式串 p[j + 1] 匹配成功,j 向前移动一个位置,即 j++,表示模式串的匹配长度增加了。
        if (j == n)
        {
            printf("%d ", i - n);
            j = ne[j];
        }
//如果 j == n,表示模式串 p 的所有字符都成功匹配,找到了模式串在文本中的一个完整出现。
//输出 i - n,即模式串 p 在文本 s 中匹配的起始位置(因为 i 是当前匹配完成的位置,所以起始位置是 i- n)。
//然后,j = ne[j]:回退 j 到上一次匹配的最长公共前缀的位置,继续尝试匹配后续文本,可能会有重叠的匹配。
    }
例子

假设 s = "abababac"p = "abac",计算后的 next 数组为 [0, 0, 1, 2]

  1. i = 1, j = 0

    • s[1] = 'a'p[1] = 'a',匹配成功,j++,即 j = 1
  2. i = 2, j = 1

    • s[2] = 'b'p[2] = 'b',匹配成功,j++,即 j = 2
  3. i = 3, j = 2

    • s[3] = 'a'p[3] = 'a',匹配成功,j++,即 j = 3
  4. i = 4, j = 3

    • s[4] = 'b'p[4] = 'c',不匹配,j = ne[3] = 1,即回退到 p[2]
  5. 继续匹配

    • 重复这个过程直到找到完整的模式串匹配。

标签:匹配,int,ne,模式,next,算法,KMP,下标
From: https://blog.csdn.net/PGeorge6/article/details/141264919

相关文章

  • CRC算法原理、推导及实现
    CRC,CyclicRedundancyCheck,循环冗余校验1.基本原理CRC的本质是除法,把待检验的数据当作一个很大(很长)的被除数,两边选定一个除数(有的文献叫poly),最后得到的余数就是CRC的校验值。判定方法:将消息和校验和分开。计算消息的校验和(在附加W个零后),并比较两个校验和。把校验......
  • 加密算法
    加密算法概述加密算法是保护信息安全的重要工具,广泛应用于数据传输、存储和身份验证等领域。根据不同的目的和特性,加密算法可以分为几类。一、对称加密算法对称加密算法是指加密和解密使用相同的密钥。其优点是速度快,适合大数据量的加密,但密钥管理是一个挑战。1.DES(数据加密......
  • 算法模板
    拓扑排序点击查看代码publicBooleanbfsOptimize(intn,int[][]edges){//初始化入度数组,每个节点有两个元素,第一个元素表示入度,第二个元素暂时未使用int[][]indegrees=newint[n][2];//初始化邻接表,用于存储图的连接关系List<List<Integer>>adj......
  • 基于协同过滤推荐算法+springboot+vue的篮球球队网站
    博主主页:猫头鹰源码博主简介:Java领域优质创作者、CSDN博客专家、阿里云专家博主、公司架构师、全网粉丝5万+、专注Java技术领域和毕业设计项目实战,欢迎高校老师\讲师\同行交流合作​主要内容:毕业设计(Javaweb项目|小程序|Python|HTML|数据可视化|SSM|SpringBoot|Vue|Jsp|PHP......
  • 掌握一致性哈希算法
    如何分配请求?大多数网站背后肯定不是只有一台服务器提供服务,因为单机的并发量和数据量都是有限的,所以都会用多台服务器构成集群来对外提供服务。但是问题来了,现在有那么多个节点(后面统称服务器为节点,因为少一个字),要如何分配客户端的请求呢?其实这个问题就是「负载均衡问......
  • C/C++算法概述
    摘要1.性能优势:C/C++语言以其接近硬件的特性而著称,提供了对底层硬件的直接控制能力。这意味着算法可以实现更高的执行效率,特别是在需要处理大量数据或实时性能要求较高的场景中。2.灵活性:C/C++提供了丰富的数据结构和操作,允许开发者以灵活的方式实现复杂的算法。同时,C++的......
  • 红温刷算法题(C/C++)
    此文章主要是给刷算法题的小萌新写的题解,博主每日刷题,把所刷的题以及所获都会发到博客里面,文章有详解思路,并且有对应的AC代码,希望我的博客对算法小萌新有所帮助。感谢CSDN平台给我这个机会,我会努力创作,创作高质量文章。 P1002[NOIP2002普及组]过河卒题目描述棋盘上 A ......
  • 《机器学习》 KNN算法、数据可视化 No.1
    一、了解机器学习1、什么是机器学习        机器学习是一种人工智能(AI)的分支,旨在让计算机通过数据自动学习和改进。机器学习算法被设计用于从数据中提取模式和规律,然后利用这些模式和规律来做出预测或做出决策,而无需明确的程序指令。        机器学习的基本......
  • 看demo学算法之 循环神经网络(RNN)
    今天我们来聊聊神经网络中的“记忆大师”——循环神经网络(RNN)。想象一下,你正在看电影,每一帧都连贯着前一帧的故事情节。RNN就像是这样一位观众,它能记住之前看到的内容,帮助理解当前的画面。是不是很酷?......
  • 【Python SHA256 摘要算法】
    SHA256是一种广泛使用的密码散列函数,用于生成数据的唯一指纹,即散列值。它属于SHA-2家族,该家族还包括SHA-384和SHA-512算法。SHA256算法在许多领域都有应用,例如:数据完整性验证:用于验证数据在传输或存储过程中是否被篡改。例如,在下载软件时,通常会提供软件的SHA256哈......