【知识点】KMP算法详解

KMP算法

算法简介

KMP算法,即看毛片 KnuthMorrisPratt 算法。是由三位计算机科学家 D.E.KnuthJ.H.MorrisV.R.Pratt 提出的。该算法可以在 O(n+m) 的时间复杂度内查找一个字符串在另一个字符串中的位置。

KMP算法的基本原理就是寻找模式串的公共前后缀,以优化时间复杂度。

算法原理

盗用百度的图片推荐阅读

首先,假设我们有一个字符串和一个需要比对的“模式串”,如图:

首先,我们一个华(bao)丽(li)的开头,就是把模式串与子串进行逐位匹配:

但是我们发现第六个字符不匹配。
按照传统的思路,我们需要将模式串右移一位,然后继续诸位比较。但是KMP算法是一个毒瘤高级算法,怎么能容忍如此赤裸裸的暴力呢?

我们发现目前已经匹配的子串中,前缀和后缀都是一样的,都是“GTG”:

所以我们惊讶地发现,我们可以直接把模式串右移到最长可匹配后缀的位置,然后继续愉快的比较

可是又出现了一个“坏字符”,我们应该如何处理呢?
没错,继续寻找可匹配最长前缀和后缀,然后再次移动模式串。

以此类推。这就是KMP算法的流程。

原理应该理解了,那么要如何实现呢?

算法实现

一、 数组

nxt 是一个一维整型数组,数组的下标代表了“已匹配前缀的下一个位置”,元素的值则是“最长可匹配前缀子串的下一个位置”。如图所示:

其中,由于子串“G”、“GT”、“GTGTGC”没有可匹配的前缀和后缀,所以对应的 nxt 值为 0

只要我们求出 nxt 数组,我们就可以解决寻找最长可匹配前后缀并移动模式串的问题了。

相信聪明的你已经理解了

二、求出 数组

我们设两个变量,ij ,它们分别表示“已匹配前缀的下一个位置”,也就是待填充的数组下标,和“最长可匹配前缀子串的下一个位置”,也就是待填充的数组元素值。它们的初始值如下:

求出 nxt 数组的过程,就是用模式串“自己匹配自己”。

首先,我们让 i++

此时,一匹配子串长度为 1 ,不存在可匹配后缀,故 nxt[1]=0

我们令 i 继续 +1
可以发现,最长可匹配前后缀子串仍不存在,所以 nxt[2]=0

i 又一次加一时,我们发现此时的模式串 s 中存在 s[j]=s[i1] ,于是 nxt[3]=nxt[2]+1=1

现在,我们需要让 i, j 都加一。

我们惊讶的发现,s[j]=s[i1]=T ,所以可匹配最长前后缀子串的长度加一,即 nxt[4]=nxt[3]+1=2

i, j 再次同时加一后,我们找到了 s[j]=s[i1]=G,所以 nxt[5]=nxt[4]+1=3

可是此时的 s[j]s[i1] 不匹配了,怎么办呢?

按照套路,应该移动模式串了。如何移动?简单地移动一位吗?当然不,我们令 j=nxt[j]

可是天不从人愿,我们发现字符仍然不匹配。所以再次使 j=nxt[j]

此时, j 已经无法回溯,所以 nxt[6]=0nxt 数组就求出来了。
推导完毕。匹配的过程也类似。

建议将 nxt 数组的推导多看几遍,这样可以加深理解。因为 nxt 数组的推导过程是KMP算法最反人类核心的地方。

具体代码实现

Talking is cheap, show me the code.

void KMP(char *s1,char *s2)
{
/* KMP算法
* @params s1为主串,s2为模式串,l1,l2分别是它们的长度
* @from 代码来自Luogu P3375,是一道KMP模板题
*/
int l1=strlen(s1),l2=strlen(s2);
int j=0;
for(int i=2;i<=l2;i++)
{
while(j && s2[i]!=s2[j+1])
j=nxt[j];
if(s2[i]==s2[j+1]) j++;
nxt[i]=j;
}
j=0;
for(int i=1;i<=l1;i++)
{
while(j && s1[i]!=s2[j+1])
j=nxt[j];
if(s1[i]==s2[j+1]) j++;
if(j==l2)
printf("%d\n",i-l2+1),j=nxt[j];
}
}

注:GalaxOJKMP 练习题题解已经在路上啦~

恭喜你找到了练习题链接~

本文作者:ExplodingKonjac

本文链接:https://www.cnblogs.com/ExplodingKonjac/p/13468780.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   ExplodingKonjac  阅读(763)  评论(0编辑  收藏  举报
编辑推荐:
· 从问题排查到源码分析:ActiveMQ消费端频繁日志刷屏的秘密
· 一次Java后端服务间歇性响应慢的问题排查记录
· dotnet 源代码生成器分析器入门
· ASP.NET Core 模型验证消息的本地化新姿势
· 对象命名为何需要避免'-er'和'-or'后缀
阅读排行:
· 编程神器Trae:当我用上后,才知道自己的创造力被低估了多少
· 开发的设计和重构,为开发效率服务
· 从零开始开发一个 MCP Server!
· Ai满嘴顺口溜,想考研?浪费我几个小时
· 从问题排查到源码分析:ActiveMQ消费端频繁日志刷屏的秘密
点击右上角即可分享
微信分享提示
💬
评论
📌
收藏
💗
关注
👍
推荐
🚀
回顶
收起