<学习笔记>KMP(MP)算法

来源:互联网 发布:windows sdk编程是啥 编辑:程序博客网 时间:2024/05/16 12:09

*(ノ゚▽゚)ノ对方不想和你说话并且向你扔出了一个题。

【题目】 luogu 3375 KMP字符串匹配

题目描述

如题,给出两个字符串s1和s2,其中s2为s1的子串,求出s2在s1中所有出现的位置。

为了减少骗分的情况,接下来还要输出子串的前缀数组next。

(如果你不知道这是什么意思也不要问,去百度搜[kmp算法]学习一下就知道了。)

输入输出格式

输入格式:
第一行为一个字符串,即为s1(仅包含大写字母)

第二行为一个字符串,即为s2(仅包含大写字母)

输出格式:

若干行,每行包含一个整数,表示s2在s1中出现的位置

接下来1行,包括length(s2)个整数,表示前缀数组next[i]的值。

输入输出样例

输入样例#1:
ABABABC
ABA
输出样例#1:
1
3
0 0 1

说明

时空限制:1000ms,128M

数据规模:

设s1长度为N,s2长度为M

对于30%的数据:N<=15,M<=5

对于70%的数据:N<=10000,M<=100

对于100%的数据:N<=1000000,M<=1000

Ps:由于我打的字符串下标是以0为开头的,学的也是以0为开头的,将就看吧。。。

对于这个题,让我们先抛开kmp,想想可以怎么解_(:з」∠)_
首先想到的是暴力匹配。
对于s1和s2,暴力枚举每一个点并进行匹配,由于每次进行匹配时时间复杂度为O(m),至少需要扫(n-m)个点,时间复杂度近似O(nm),TLE!
优化:每次匹配时如果有不同的就直接跳过。

依旧会T…orz

那么…(ノ゚▽゚)ノ kmp…

KMP算法简介

kmp算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt同时发现,因此人们称它为克努特——莫里斯——普拉特操作(简称KMP算法)。KMP算法的关键是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是实现一个next()函数,函数本身包含了模式串的局部匹配信息。时间复杂度O(m+n)。(摘自百度百科)

O(m): 对s2的预处理
O(n) : 扫一遍s1,完成匹配

当匹配的过程中发现s1[i]与s2[j]不同时
如 匹配到c和a发现他们不同:

这里写图片描述

暴力会把aabaabaa往后移一位重新比较。
而kmp算法会因为c之前的已经比较过,我们已经知道s1前面的部分为aabaaba了,显然后移一位不可能匹配成功,后移两位也不可能匹配成功,而后移三位是有可能的。所以kmp算法会直接后移三位后再比对c和s2的第5个字符。

由于s2已经给出,所以我们可以预先处理出当s2[i]匹配失败时转移后应继续判断的位置(s2可能能继续匹配的位置 or s2后移的位数)。 这里用fail数组表示。
如上图中的 fail[7]=4;

可以写出kmp的代码为

void Kmp(){    n=strlen(s1),m=strlen(s2);    Done_fail();     for(int i=0,j=0;i<n;++i) // j既可以理解为下标,也可以理解为当前已经匹配了j个字符(0开头的好处2333)    {        while(j&&s1[i]!=s2[j]) j=fail[j]; // 如果不匹配,就把j往前跳(s2往后移),直到j=0或可以匹配为止        if(s1[i]==s2[j]) ++j; // 可以匹配 j++;        if(j==m) Ans[++ans]=i-m+1,j=fail[j];  // 匹配成功,记录下出现的位置并更新j    }}

难点:

fail数组的处理:

递推求法:

void Done(){    fail[0]=fail[1]=0;     for(int i=1,k=0;i<m;++i)    {        k=fail[i]; // 已经匹配了i(0~i-1)个字符的fail值,也就是i的fail值,此时我们并不知道s2[i]是否已经/可以匹配,通过判断s2[i],我们用fail[i]来更新fail[i+1]。        while(k&&s2[i]!=s2[k]) k=fail[k];// 如果s2[i]!=s2[k](s2[k]为s2可能能继续匹配的字符),说明s2[i+1]如果失配了,我们并不能直接转移到s2[k+1]尝试匹配。所以我们就像上面的kmp代码一样,不断往前跳,直到找到一个可以匹配的地方为止。(就如同字符串失配了一样)        fail[i+1]=(s2[i]==s2[k])?k+1:0;    }}

建议自己脑跑一遍以加深理解。
栗子:

i 0 1 2 3 4 5 6 7 8 9 10 11 s2[i] a a b a a b a a c a b 无 fail[i] 0 0 1 0 1 2 3 4 5 0 1 0

网上的博客里还有另一1种理解方法,用next数组表示,统计s2”前缀”和”后缀”的最长的共有元素的长度,next[i]表示s2中前i个字符的”前缀”和”后缀”的最长的共有元素的长度,如 ABCDABBD 对应的next 为 0,0,0,0,1,2,0,0 。所以next[i-1]可以直接用作下标为i的字符失配时应转移到的位置。也就是说fail是next统一往后移了一位。其实思想是一样的。(next应该会更好懂一些)

摘自网上的next代码。

void Done_next(){    next[0] = 0;    for (int i = 1,k = 0; i < m; ++i) // k为最大长度    {        while(k > 0 && s2[i] != s2[k])             k = next[k-1];               if (s2[i] == s2[k])        {            k++;        }        next[i] = k;    }}

那么回到原题,发现只是个模板题,直接上代码好了(:3_ヽ)_

注:原题的字符串是以1为开头的,另外求的也是next数组

#include<iostream>#include<cstdio>#include<cmath>#include<cstring>#include<cstdlib>using namespace std;int n,m,ans;int fail[1010],Ans[1000010];char a[1000010],b[1010];void Done(){    fail[0]=fail[1]=0;    for(int i=1,k=0;i<m;++i)    {        while(k&&b[i]!=b[k]) k=fail[k];        fail[i+1]=b[i]==b[k]?++k:0;    }}void Kmp(){    n=strlen(a),m=strlen(b);    Done();    for(int i=0,j=0;i<n;++i)    {        while(j&&a[i]!=b[j]) j=fail[j];        if(a[i]==b[j]) ++j;        if(j==m) Ans[++ans]=i-m+1,j=fail[j];    }}int main(){    cin>>a>>b;    Kmp();    //cout<<ans<<'\n';      for(int i=1;i<=ans;++i)      cout<<Ans[i]+1<<'\n';  // 以一为开头    for(int i=1;i<=m;++i)  // 往前移一位    cout<<fail[i]<<' ';    cout<<'\n';    return 0;} 

ヾノ≧∀≦)o βyё βyё..

The end.

原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 网易将军令换手机了怎么办 须弥bb有弱点土怎么办 战网密码忘记了怎么办 梦三账号忘了怎么办 快手手机号被注册了怎么办 快手该手机号已注册怎么办 手机号被别人注册了快手怎么办 快手显示手机号已注册怎么办 163的邮箱忘了怎么办 河长制账号密码忘了怎么办 网易登录名忘了怎么办 网易通行证安全手机忘了怎么办 美团数据获取失败怎么办 扣扣图片加载不出来怎么办 电脑qq最小化后不见了怎么办 uwp桌面快捷图标显示异常怎么办 网易云自动切歌怎么办 捡到小米手机怎么办才能自己用 dnf启动安装程序出错怎么办 苹果手机相册视频下载出错怎么办 苹果7软件闪退怎么办 微信上有钱账号就是怎么办 手机网速不给力怎么办 xp系统登录密码忘了怎么办 手机百度云网络出错怎么办 百度账号提示异常风险怎么办 刷xp框架卡米怎么办 全民k歌歌曲下架怎么办 全民k歌伴奏下架怎么办 苹果手机图片的图标打不开怎么办 电脑页面加载不出来怎么办 电脑主页面加载不出来怎么办 桌面图标不见了怎么办右键无反应 手机一直闪退该怎么办 点击华为设置闪退怎么办 小白摄像头获取视频文件失败怎么办 为什么解压文件老提示失败怎么办 b站sd卡写入失败怎么办 手机检测不到sd卡怎么办 华为sd卡不可用怎么办 u盘延缓写入失败怎么办