KMP算法(推导方法及模板)
介绍
克努斯-莫里斯-普拉特算法Knuth-Morris-Pratt字符串查找算法(简称为KMP算法)可在一个主文本字符串S内查找一个词W的出现位置。此算法通过运用对这个词在不匹配时本身就包含足够的信息来确定下一个匹配将在哪里开始的发现,从而避免重新检查先前匹配的字符。
此算法可以在O(n+m)时间数量级上完成串的模式匹配操作,其改进在于:每当一趟匹配过程中出现字符比较不等时,不需回溯i的指针,而是利用已经得到的“部分匹配”的结果将模式向右“滑动”尽可能远的距离后,继续进行比较。
kmp的核心之处在于next数组,而为了方便理解,我先介绍KMP的思想
KMP匹配
当开始匹配时,如果匹配过程中产生“失配”时,指针i(原串的下标)不变,指针j(模式串的下标)退回到next[j] 所指示的位置上重新进行比较,并且当指针j退回至零时,指针i和指针j需同时加一。即主串的第i个字符和模式的第一个字符不等时,应从主串的第i+1个字符起重新进行匹配。
简单来说,就是两个串匹配,如果当前字符相等就比较两个字符串的下一个字符,如果当前匹配不相等时,就让j(待匹配串的下标)回到next[j] 的位置,因为我们已经知道next数组的作用是利用已经得到的“部分匹配”的结果将模式向右“滑动”尽可能远的距离,如ababac与abac比较时i=4,j=4时不匹配,则利用next数组让j=2继续匹配而不用重新开始。(目前先不用管next数组的值时如何得到的,只要明白它的作用即可,下面回介绍)
所以我们可以写出kmp的代码
int KMP(char str[],char pat[])
{
int lenstr=strlen(str);
int lenpat=strlen(pat);
int i=1,j=1;
while(i<=lenstr)
{
if(j==0 || str[i]==pat[j]) //匹配成功继续往后匹配
++i,++j;
else
j=next[j]; //否则根据next数组继续匹配
if(j==lenpat) //说明匹配完成
return 1;
}
return 0;
}
接下来就是关键的求next数组了
next数组
首先,next数组取决于模式串本身而与相匹配的主串无关,我们可以对其递推得到。
网上讲next数组求解的博客一大堆,我就不那样从定义那一扯一大堆了,随便说一下如何推算的吧。
举个例子,如abacabc,首先,next[ 1 ]=0(下标从1开始),后面next[j]的值就看第j-1个字符是否与前面的匹配,如果匹配next[j]=next[j-1]+1,否则其他情况next[j]=1。这样说太笼统,我们看例子:
next[2]=1(第一个字符无法匹配,所以为1),
next[3]=1(第二个字符与第一个字符不相等),
next[4]=2(第三个字符与第一个字符相匹配,所以就等于next[3]+1),
next[5]=1(因为第4个字符与前面没有匹配的),
next[6]=2(同样第5个字符与第1个字符匹配),
next[7]=3(因为第6个字符b与第2个字符b匹配,同时由next[6]可知第5个字符与第1个字符同样匹配,即子串ab与ab匹配,故next[7]=next[6]+1)
下面给出几个例子,可以自己推导一下
abcdex
011111
abcabx
011123
ababaaaba
011234223
aaaaaaaab
012345678
如果理解了推导过程的话再回头看代码就好理解了,就算不理解也不要紧,先用着,过一段时间再去消化,下面是代码:
void getnext(char *pat)
{
int i=1,j=0;
int len=strlen(pat);
next[1]=0;
while(i<len)
{
if(j==0 || pat[i]==pat[j])
{
++i;
++j;
next[i]=j;
}
else
j=next[j];
}
优化
前面定义的next在某些情况下有缺陷,如模式串aaaab和主串aaabaaaab匹配时,仍有许多不必要的步骤,所以下面代码在这种情况做了优化:
void getnext(char *pat)
{
int i=1,j=0;
int len=strlen(pat);
next[1]=0;
while(i<len)
{
if(j==0 || pat[i]==pat[j])
{
++i;
++j;
if(pat[i]!=pat[j])
next[i]=j;
else
next[i]=next[j];
}
else
j=next[j];
}
模板
而下面是常用的模板,可以找到匹配下标与匹配次数,在与前面的略有些不同,其实就是next的值都减1罢了。
#include <iostream>
#include<cstring>
#include<cstdio>
using namespace std;
char str[1000010],pat[1000010];//pat为模式串,str为主串
int Next[1000010]; //Next[x]下标x表示匹配失败处字符下标
//模式串pat的前缀与x位置的后缀的最大匹配字符个数-1
void GetNext(char *pat)
{
int LenPat = strlen(pat);
int i = 0,j = -1;
Next[0] = -1;
while(i < LenPat)
{
if(j == -1 || pat[i] == pat[j])
{
i++,j++;
Next[i] = j;
}
else
j = Next[j];
}
}
int KMP()//返回模式串pat在str中第一次出现的位置
{
int LenStr = strlen(str);
int LenPat = strlen(pat);
GetNext(pat);
int i = 0,j = 0;
int ans = 0;//计算模式串在主串匹配次数
while(i < LenStr)
{
if(j == -1 || str[i] == pat[j])
i++,j++;
else
j = Next[j];
if(j == LenPat)
{
//ans++; ans存放匹配次数,去掉return,最后返回ans
return i - LenPat + 1;
}
}
return -1;//没找到匹配位置
//return ans;//返回匹配次数。
}
int main()
{
scanf("%s%s",str,pat);
int i=KMP();
printf("%d\n",i);
return 0;
}
KMP算法(推导方法及模板)的更多相关文章
- KMP算法,匹配字符串模板(返回下标)
//KMP算法,匹配字符串模板 void getNext(int[] next, String t) { int n = next.length; for (int i = 1, j = 0; i & ...
- 什么是KMP算法?KMP算法推导
花了大概3天时间,了解,理解,推理KMP算法,这里做一次总结!希望能给看到的人带来帮助!! 1.什么是KMP算法? 在主串Str中查找模式串Pattern的方法中,有一种方式叫KMP算法 KMP算法是 ...
- KMP算法自我理解 和 模板
字符串 abcd abc abcd abc 匹配串 cdabcd 匹配串的 next 0 0 0 0 1 2: 开始匹配 abcd abc abcd abc cd abc d a,d 匹配失 ...
- 解读KMP算法
前后断断续续搞了5个月,每次都以为自己懂了, 但是要写的时候都不知从何下手,然后又是各种找博客,看帖子,所以这次试着用自己的语言写一个博客. 首先,KMP算法就是从一个模板字符串(S) 中匹配目标字符 ...
- KMP算法与传统字符串寻找算法
原理:KMP算法是一种模板匹配算法,它首先对模板进行便利,对于模板中与模板首字符一样和首字符进行标志-1,对于模板匹配中出现不匹配的若是第一轮检查标志为0,若不是第一轮检查标志为该元素与标志为-1的距 ...
- Luogu 3375 【模板】KMP字符串匹配(KMP算法)
Luogu 3375 [模板]KMP字符串匹配(KMP算法) Description 如题,给出两个字符串s1和s2,其中s2为s1的子串,求出s2在s1中所有出现的位置. 为了减少骗分的情况,接下来 ...
- [模板]KMP算法
昨天晚上一直在调KMP(模板传送门),因为先学了hash[关于hash的内容会在随后进行更(gu)新(gu)]于是想从1开始读...结果写出来之后一直死循环,最后我还是改回从0读入字符串了. [预先定 ...
- KMP算法(——模板习题与总结)
KMP算法是一种改进的模式匹配算法,相比于朴素的模式匹配算法效率更高.下面讲解KMP算法的基本思想与实现. 先来看一下朴素模式匹配算法的基本思想与实现. 朴素模式匹配算法的基本思想是匹配过程中如果该位 ...
- KMP算法模板&&扩展
很不错的学习链接:https://blog.csdn.net/v_july_v/article/details/7041827 具体思路就看上面的链接就行了,这里只放几个常用的模板 问题描述: 给出字 ...
随机推荐
- [bzoj2124]等差子序列_线段树_hash
等差子序列 bzoj-2124 题目大意:给定一个1~n的排列,问是否存在3个及以上的位置上的数构成连续的等差子序列. 注释:$1\le n\le 10^4$. 想法:这题就相当于是否存在3个数i,j ...
- Swift学习——变量var和let常量的用法(一)
Swift中的变量var和let常量 首先介绍一下Swift中的 var 和 let (1)var 是 variable的缩写形式,是变量的意思 ,是可改变的.并非数据类型 比如: 注意每一个语句后面 ...
- MongoDB 索引的使用, 管理 和优化
MongoDB 索引的使用, 管理 和优化 2014-03-25 17:12 6479人阅读 评论(0) 收藏 举报 分类: MongoDB(9) [使用explain和hint] 前面讲高级查询 ...
- [Linux]非常方便的上传下载文件工具rz和sz
linux上非常方便的上传下载文件工具rz和sz (本文适合linux入门的朋友) [一般用于SecureCRT ssh中使用] █ 法一:直接用yum安装lrzsz(推荐) yum insta ...
- [Swift]注册并购买加入Apple开发者计划。提示: “你的支付授权失败。请核对你的信息并重试,或尝试其他支付方式。请联系你的银行”
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★➤微信公众号:山青咏芝(shanqingyongzhi)➤博客园地址:山青咏芝(https://www.cnblogs. ...
- springboot @WebFilter过滤器的使用
过滤器的用法就不多说了 新建Filter的继承类:MemberFilter(放置包需要注意) @WebFilter(urlPatterns = "/*") @Order(1) pu ...
- Laravel-查询作用域
Laravel-查询作用域 标签(空格分隔): php, laravel 全局作用域 ## 编写全局作用域 ## 编写全局作用域很简单.定义一个实现 Illuminate\Database\Eloqu ...
- 24. Swap Nodes in Pairs[M]两两交换链表中的节点
题目 Given a linked list, swap every two adjacent nodes and return its head. You may not modify the va ...
- mysql 强制修改密码
mysql忘记密码时强制修改步骤如下: 1.用命令编辑配置文件/etc/my.cnf 2.添加一条语句使其变为不用密码就能进入的状态 skip-grant-tables 3.保存并退出,然后再命令行输 ...
- BZOJ 1951 Lucas定理+CRT
思路: 枚举约数 套个裸的Lucas+CRT就完了... //By SiriusRen #include <cmath> #include <cstdio> using nam ...