【知识点】KMP算法详解
KMP算法
算法简介
KMP算法,即看毛片 算法。是由三位计算机科学家 提出的。该算法可以在 的时间复杂度内查找一个字符串在另一个字符串中的位置。
KMP算法的基本原理就是寻找模式串的公共前后缀,以优化时间复杂度。
算法原理
盗用百度的图片(推荐阅读)
首先,假设我们有一个字符串和一个需要比对的“模式串”,如图:
首先,我们一个华(bao)丽(li)的开头,就是把模式串与子串进行逐位匹配:
但是我们发现第六个字符不匹配。
按照传统的思路,我们需要将模式串右移一位,然后继续诸位比较。但是KMP算法是一个毒瘤高级算法,怎么能容忍如此赤裸裸的暴力呢?
我们发现目前已经匹配的子串中,前缀和后缀都是一样的,都是“GTG”:
所以我们惊讶地发现,我们可以直接把模式串右移到最长可匹配后缀的位置,然后继续愉快的比较
可是又出现了一个“坏字符”,我们应该如何处理呢?
没错,继续寻找可匹配最长前缀和后缀,然后再次移动模式串。
以此类推。这就是KMP算法的流程。
原理应该理解了,那么要如何实现呢?
算法实现
一、 数组
是一个一维整型数组,数组的下标代表了“已匹配前缀的下一个位置”,元素的值则是“最长可匹配前缀子串的下一个位置”。如图所示:
其中,由于子串“G”、“GT”、“GTGTGC”没有可匹配的前缀和后缀,所以对应的 值为 。
只要我们求出 数组,我们就可以解决寻找最长可匹配前后缀并移动模式串的问题了。
相信聪明的你已经理解了
二、求出 数组
我们设两个变量, 和 ,它们分别表示“已匹配前缀的下一个位置”,也就是待填充的数组下标,和“最长可匹配前缀子串的下一个位置”,也就是待填充的数组元素值。它们的初始值如下:
求出 数组的过程,就是用模式串“自己匹配自己”。
首先,我们让 。
此时,一匹配子串长度为 ,不存在可匹配后缀,故 。
我们令 继续 。
可以发现,最长可匹配前后缀子串仍不存在,所以 。
当 又一次加一时,我们发现此时的模式串 中存在 ,于是 。
现在,我们需要让 都加一。
我们惊讶的发现, ,所以可匹配最长前后缀子串的长度加一,即 。
当 再次同时加一后,我们找到了 ,所以
可是此时的 和 不匹配了,怎么办呢?
按照套路,应该移动模式串了。如何移动?简单地移动一位吗?当然不,我们令 。
可是天不从人愿,我们发现字符仍然不匹配。所以再次使 。
此时, 已经无法回溯,所以 , 数组就求出来了。
推导完毕。匹配的过程也类似。
建议将 数组的推导多看几遍,这样可以加深理解。因为 数组的推导过程是KMP算法最反人类核心的地方。
具体代码实现
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]; } }
注: 的 练习题题解已经在路上啦~
本文作者:ExplodingKonjac
本文链接:https://www.cnblogs.com/ExplodingKonjac/p/13468780.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】还在用 ECharts 开发大屏?试试这款永久免费的开源 BI 工具!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从问题排查到源码分析:ActiveMQ消费端频繁日志刷屏的秘密
· 一次Java后端服务间歇性响应慢的问题排查记录
· dotnet 源代码生成器分析器入门
· ASP.NET Core 模型验证消息的本地化新姿势
· 对象命名为何需要避免'-er'和'-or'后缀
· 编程神器Trae:当我用上后,才知道自己的创造力被低估了多少
· 开发的设计和重构,为开发效率服务
· 从零开始开发一个 MCP Server!
· Ai满嘴顺口溜,想考研?浪费我几个小时
· 从问题排查到源码分析:ActiveMQ消费端频繁日志刷屏的秘密