KMP匹配(模板)
先粘上我入门KMP时看的大佬的博客:orz orz
我觉得这篇已经讲的很详细了,希望大家能坚持看下去。
步骤
①寻找前缀后缀最长公共元素长度
对于P = p0 p1 ...pj-1 pj,寻找模式串P中长度最大且相等的前缀和后缀。如果存在p0 p1 ...pk-1 pk = pj- k pj-k+1...pj-1 pj,那么在包含pj的模式串中有最大长度为k+1的相同前缀后缀。
举个例子,如果给定的模式串为“abab”,那么它的各个子串的前缀后缀的公共元素的最大长度如下表格所示:
比如对于字符串aba来说,它有长度为1的相同前缀后缀a;而对于字符串abab来说,它有长度为2的相同前缀后缀ab(相同前缀后缀的长度为k + 1,k + 1 = 2)。
②求next数组
next 数组考虑的是除当前字符外的最长相同前缀后缀,所以通过第①步骤求得各个前缀后缀的公共元素的最大长度后,只要稍作变形即可:将第①步骤中求得的值整体右移一位,然后初值赋为-1,如下表格所示:
比如对于aba来说,第3个字符a之前的字符串ab中有长度为0的相同前缀后缀,所以第3个字符a对应的next值为0;而对于abab来说,第4个字符b之前的字符串aba中有长度为1的相同前缀后缀a,所以第4个字符b对应的next值为1(相同前缀后缀的长度为k,k = 1)。
③根据next数组进行匹配
匹配失配,j = next [j],模式串向右移动的位数为:j - next[j]。换言之,当模式串的后缀pj-k pj-k+1, ..., pj-1 跟文本串si-k si-k+1, ..., si-1匹配成功,但pj 跟si匹配失败时,因为next[j] = k,相当于在不包含pj的模式串中有最大长度为k 的相同前缀后缀,即p0 p1 ...pk-1 = pj-k pj-k+1...pj-1,故令j = next[j],从而让模式串右移j - next[j] 位,使得模式串的前缀p0 p1, ..., pk-1对应着文本串 si-k si-k+1, ..., si-1,而后让pk 跟si 继续匹配。如下图所示:
综上,KMP的next 数组相当于告诉我们:
当模式串中的某个字符跟文本串中的某个字符匹配失配时,模式串下一步应该跳到哪个位置。
如模式串中在j 处的字符跟文本串在i 处的字符匹配失配时,下一步用next [j] 处的字符继续跟文本串i 处的字符匹配,相当于模式串向右移动 j - next[j] 位。
下面给出模板:
next数组的求法:
void GetNext(char* p,int next[])
{
int pLen = strlen(p);
next[] = -;
int k = -;
int j = ;
while (j < pLen - )
{
//p[k]表示前缀,p[j]表示后缀
if (k == - || p[j] == p[k])
{
++k;
++j;
next[j] = k;
}
else
{
k = next[k];
}
}
}
求next数组的改进版:
//优化过后的next 数组求法
void GetNextval(char* p, int next[])
{
int pLen = strlen(p);
next[] = -;
int k = -;
int j = ;
while (j < pLen - )
{
//p[k]表示前缀,p[j]表示后缀
if (k == - || p[j] == p[k])
{
++j;
++k;
//较之前next数组求法,改动在下面4行
if (p[j] != p[k])
next[j] = k; //之前只有这一行
else
//因为不能出现p[j] = p[ next[j ]],所以当出现时需要继续递归,k = next[k] = next[next[k]]
next[j] = next[k];
}
else
{
k = next[k];
}
}
}
KMP算法:
int KmpSearch(char* s, char* p)
{
int i = ;
int j = ;
int sLen = strlen(s);
int pLen = strlen(p);
while (i < sLen && j < pLen)
{
//①如果j = -1,或者当前字符匹配成功(即S[i] == P[j]),都令i++,j++
if (j == - || s[i] == p[j])
{
i++;
j++;
}
else
{
//②如果j != -1,且当前字符匹配失败(即S[i] != P[j]),则令 i 不变,j = next[j]
//next[j]即为j所对应的next值
j = next[j];
}
}
if (j == pLen)
return i - j;
else
return -;
}
自己习惯用的模板(可忽略)
int Next[];
char str1[];
char str2[]; void getnext(char *str)
{
int len=strlen(str);
int j=;
int k=-;
Next[]=-;
while(j<len)
{
if(k==-||str[j]==str[k])
{
j++;
k++;
if(str[j]!=str[k])
{
Next[j]=k;
}
else
Next[j]=Next[k];
}
else
k=Next[k];
}
} int KMP(char *str1,char *str2)
{
int len1=strlen(str1);
int len2=strlen(str2);
int i=;
int j=;
while(i<len1&&j<len2)
{
if(j==-||str1[i]==str2[j])
{
i++;
j++;
}
else
{
j=Next[j];
} }
if(j==len2)
return i-j;
else
return -;
}
觉得模板太长?看看这种方法吧:
https://www.cnblogs.com/eternhope/p/9481643.html
下面是一些入门例题:
HDU-1711 Number Sequence
http://acm.hdu.edu.cn/showproblem.php?pid=1711
Problem Description
Input
Output
Sample Input
Sample Output
-
题意:查找子串B在字符串A中第一次出现位置
模板题,直接套板子
#include <stdio.h>
#include <string.h>
#include <iostream>
#include <string>
#include <math.h>
#include <algorithm>
#include <vector>
#include <stack>
#include <queue>
#include <set>
#include <map>
#include <math.h>
const int INF=0x3f3f3f3f;
typedef long long LL;
const int mod=1e9+;
const int maxn=1e6+;
const int maxm=1e4+;
using namespace std; int Next[maxm];
int n,m;
int A1[maxn];
int A2[maxm]; void Get_Next()
{
int j=;
int k=;
Next[]=;
while(j<m)
{
if(k==||A2[j]==A2[k])
{
j++;
k++;
if(A2[j]!=A2[k])
Next[j]=k;
else
Next[j]=Next[k];
}
else
k=Next[k];
}
} int KMP()
{
int i=;
int j=;
while(i<=n&&j<=m)
{
if(j==||A1[i]==A2[j])
{
i++;
j++;
}
else
j=Next[j];
}
if(j==m+)
return i-j+;
else
return -;
} int main()
{
int T;
scanf("%d",&T);
while(T--)
{
scanf("%d %d",&n,&m);
for(int i=;i<=n;i++)
{
scanf("%d",&A1[i]);
}
for(int i=;i<=m;i++)
{
scanf("%d",&A2[i]);
}
Get_Next();
printf("%d\n",KMP());
}
return ;
}
POJ-3461 Oulipo
http://poj.org/problem?id=3461
Description
The French author Georges Perec (1936–1982) once wrote a book, La disparition, without the letter 'e'. He was a member of the Oulipo group. A quote from the book:
Tout avait Pair normal, mais tout s’affirmait faux. Tout avait Fair normal, d’abord, puis surgissait l’inhumain, l’affolant. Il aurait voulu savoir où s’articulait l’association qui l’unissait au roman : stir son tapis, assaillant à tout instant son imagination, l’intuition d’un tabou, la vision d’un mal obscur, d’un quoi vacant, d’un non-dit : la vision, l’avision d’un oubli commandant tout, où s’abolissait la raison : tout avait l’air normal mais…
Perec would probably have scored high (or rather, low) in the following contest. People are asked to write a perhaps even meaningful text on some subject with as few occurrences of a given “word” as possible. Our task is to provide the jury with a program that counts these occurrences, in order to obtain a ranking of the competitors. These competitors often write very long texts with nonsense meaning; a sequence of 500,000 consecutive 'T's is not unusual. And they never use spaces.
So we want to quickly find out how often a word, i.e., a given string, occurs in a text. More formally: given the alphabet {'A', 'B', 'C', …, 'Z'} and two finite strings over that alphabet, a word W and a text T, count the number of occurrences of W in T. All the consecutive characters of W must exactly match consecutive characters of T. Occurrences may overlap.
Input
The first line of the input file contains a single number: the number of test cases to follow. Each test case has the following format:
- One line with the word W, a string over {'A', 'B', 'C', …, 'Z'}, with 1 ≤ |W| ≤ 10,000 (here |W| denotes the length of the string W).
- One line with the text T, a string over {'A', 'B', 'C', …, 'Z'}, with |W| ≤ |T| ≤ 1,000,000.
Output
For every test case in the input file, the output should contain a single number, on a single line: the number of occurrences of the word W in the text T.
Sample Input
BAPC
BAPC
AZA
AZAZAZA
VERDI
AVERDXIVYERDIAN
Sample Output
题意:查找子串A在字符串B中出现的次数
标准KMP是返回子串第一次出现的位置,这个是统计次数,稍微改一下就好了
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <string>
#include <vector>
using namespace std;
int Next[];
char str1[];
char str2[]; void getnext(char *str)
{
int len=strlen(str);
int j=;
int k=-;
Next[]=-;
while(j<len)
{
if(k==-||str[j]==str[k])
{
j++;
k++;
if(str[j]!=str[k])
{
Next[j]=k;
}
else
Next[j]=Next[k];
}
else
k=Next[k];
}
} int KMP(char *str1,char *str2)
{
int ans=;
int len1=strlen(str1);
int len2=strlen(str2);
int i=,j=;
while(i<len1)
{
if(j==-||str1[i]==str2[j])
{
i++;
j++;
}
else
{
j=Next[j];
}
if(j==len2)
{
ans++;
j=Next[j];
// i=i-j+1; //这两行换上一行会超时
// j=0;
}
}
return ans;
} int main()
{
int t;
scanf("%d",&t); while(t--)
{
scanf("%s %s",str1,str2);
getnext(str1);
printf("%d\n",KMP(str2,str1));
}
return ;
}
先来个next数组模板
void Get_Next()
{
int j=;
int k=-;
Next[]=-;
while(j<m)
{
if(k==-||str2[j]==str2[k])
{
j++;
k++;
Next[j]=k;
}
else
k=Next[k];
}
}
首先
1:i-next[i]是最小循环节的长度
2:字符串的循环条件是i%(i-next[i])==0&&next[i]!=0
3:最小循环节的循环次数是i / (i-next[i])
4:整体 在 字符串 s
L= strlen(s)
n = next[l]
L%(L-next[L])==0 有循环节
k = L - n= L - next[L] 最小循环节
1> p = L%k=L%(L-next[L]) 循环k节点若干次后剩余部分的长度
2> q = (k-p)%k =k-q q为字符串s1要想补齐成恰好整数个k所需要的最少字符数
对于1 ababa 没有循环节 next[L]=3 最小循环节L-next[L]=2 则 取余说明 还剩多少节点没有循环 如最后一个a
正对2如果最小循环节减去取余后的 则得到补齐整数时所需的字串
在KMP算法的使用中,首要任务就是获取一个字符串的next数组,所以我们得 明白next数组的含义(最好的方法是自己弄个例子,在草稿纸上模拟一下),在这里,通俗一点讲,next[k] 表示,在模式串的 k 个字符失配了,然后下一次匹配从 next[k] 开始(next[k] 中保存的是该失配字符的前一个字符在前面出现过的最近一次失配的字符后面的一个字符的位置,有点绕口,自己写个例子看看就明白了,也可以继续往下看,有介 绍,然后再自己尝试写写 )。
至于next数组为什么可以用来求重复前缀呢,而且求出来的重复前缀是最小的呢?
next数组的求法:
void getnext(int len){
int i=0,j=-1;
next[0]=-1;
while(i<len){
if(j==-1 || str[i]==str[j]){
i++;j++;
next[i]=j;
}else
j=next[j];
}
}
个人认为,next数组在求解的过程中,用到了KMP的思想,当前失配了,就回溯到上一个next,请见 j=next[j] ,先说个结论,如果到位置 i ,如果有 i%(i-next(i))==0 , 那说明字符串开始循环了,并且循环到 i-1 结束,为什么这样呢?
我们先假设到达位置 i-1 的时候,字符串循环了(到i-1完毕),那么如果到第i个字符的时候,失配了,根据next数组的求法,我们是不是得回溯?
然而回溯的话,由于字符串是循环的了(这个是假定的),next[i] 是不是指向上一个循环节的后面一个字符呢??
是的,上一个循环节的末尾是 next[i]-1 ,然后现在循环节的末尾是 i-1 ,然么循环节的长度是多少呢?
所以,我们有 (i - 1) - ( next[i] - 1 ) = i - next[i] 就是循环节的长度(假设循环成立的条件下),但是我们怎么知道这个循环到底成立吗?
现在我们已经假设了 0————i-1 循环了,那么我们就一共有i 个字符了,如果有 i % ( i - next[i] ) == 0,总的字符数刚好是循环节的倍数,那么说明这个循环是成立的。
注意还有一点,如果 next[i] == 0,即使符合上述等式,这也不是循环的,举个反例
0 1 2 3 4 5
a b c a b d
-1 0 0 0 1 2
下标为1,2,3的next值均为0,那么 i%(i-next【i】)=i%i==0,但是这个并不是循环。
解释完毕,然后再来看下,为什么求出来的循环节长度是最小的呢?
因为next数组失配的时候,总是回溯到最近的循环节,所以i-next【i】就是最小的循环节长度
为什么求出来的循环次数是最多的呢?
循环节长度是最小的了,那么循环次数肯定是最多的了。
总结一下,如果对于next数组中的 i, 符合 i % ( i - next[i] ) == 0 && next[i] != 0 , 则说明字符串循环,而且
循环节长度为: i - next[i]
循环次数为: i / ( i - next[i] )
https://blog.csdn.net/weixin_43768644/article/details/94644776
https://blog.csdn.net/dyx404514/article/details/41831947
https://www.cnblogs.com/vivym/p/3927955.html
https://segmentfault.com/a/1190000008663857
https://subetter.com/algorithm/extended-kmp-algorithm.html
扩展 KMP
问题定义:给定两个字符串 S 和 T(长度分别为 n 和 m),下标从 0 开始,定义extend[i]
等于S[i]...S[n-1]
与 T 的最长相同前缀的长度,求出所有的extend[i]
。举个例子,看下表:
为什么说这是 KMP 算法的扩展呢?显然,如果在 S 的某个位置 i 有extend[i]
等于 m,则可知在 S 中找到了匹配串 T,并且匹配的首位置是 i。而且,扩展 KMP 算法可以找到 S 中所有 T 的匹配。接下来具体介绍下这个算法。
一:算法流程
(1)
如上图,假设当前遍历到 S 串位置 i,即extend[0]...extend[i - 1]
这 i 个位置的值已经计算得到。设置两个变量,a 和 p。p 代表以 a 为起始位置的字符匹配成功的最右边界,也就是 "p = 最后一个匹配成功位置 + 1"。相较于字符串 T 得出,S[a...p) 等于 T[0...p-a)。
再定义一个辅助数组int next[]
,其中next[i]
含义为:T[i]...T[m - 1]
与 T 的最长相同前缀长度,m 为串 T 的长度。举个例子:
(2)
S[i]
对应T[i - a]
,如果i + next[i - a] < p
,如上图,三个椭圆长度相同,根据 next 数组的定义,此时extend[i] = next[i - a]
。
(3)
如果i + next[i - a] == p
呢?如上图,三个椭圆都是完全相同的,S[p] != T[p - a]
且T[p - i] != T[p - a]
,但S[p]
有可能等于T[p - i]
,所以我们可以直接从S[p]
与T[p - i]
开始往后匹配,加快了速度。
(4)
如果i + next[i - a] > p
呢?那说明S[i...p)
与T[i-a...p-a)
相同,注意到S[p] != T[p - a]
且T[p - i] == T[p - a]
,也就是说S[p] != T[p - i]
,所以就没有继续往下判断的必要了,我们可以直接将extend[i]
赋值为p - i
。
(5)最后,就是求解 next 数组。我们再来看下next[i]
与extend[i]
的定义:
- next[i]:
T[i]...T[m - 1]
与 T 的最长相同前缀长度; - extend[i]:
S[i]...S[n - 1]
与 T 的最长相同前缀长度。
恍然大悟,求解next[i]
的过程不就是 T 自己和自己的一个匹配过程嘛,下面直接看代码。
二:代码
#include <iostream>
#include <string> using namespace std; /* 求解 T 中 next[],注释参考 GetExtend() */
void GetNext(string & T, int & m, int next[])
{
int a = , p = ;
next[] = m; for (int i = ; i < m; i++)
{
if (i >= p || i + next[i - a] >= p)
{
if (i >= p)
p = i; while (p < m && T[p] == T[p - i])
p++; next[i] = p - i;
a = i;
}
else
next[i] = next[i - a];
}
} /* 求解 extend[] */
void GetExtend(string & S, int & n, string & T, int & m, int extend[], int next[])
{
int a = , p = ;
GetNext(T, m, next); for (int i = ; i < n; i++)
{
if (i >= p || i + next[i - a] >= p) // i >= p 的作用:举个典型例子,S 和 T 无一字符相同
{
if (i >= p)
p = i; while (p < n && p - i < m && S[p] == T[p - i])
p++; extend[i] = p - i;
a = i;
}
else
extend[i] = next[i - a];
}
} int main()
{
int next[];
int extend[];
string S, T;
int n, m; while (cin >> S >> T)
{
n = S.size();
m = T.size();
GetExtend(S, n, T, m, extend, next); // 打印 next
cout << "next: ";
for (int i = ; i < m; i++)
cout << next[i] << " "; // 打印 extend
cout << "\nextend: ";
for (int i = ; i < n; i++)
cout << extend[i] << " "; cout << endl << endl;
}
return ;
}
数据测试如下:
aaaaabbb
aaaaac
next:
extend: abc
def
next:
extend:
先溜了,以后在填坑
KMP匹配(模板)的更多相关文章
- 【poj 3167】Cow Patterns(字符串--KMP匹配+数据结构--树状数组)
题意:给2个数字序列 a 和 b ,问按从小到达排序后,a中的哪些子串与b的名次匹配. a 的长度 N≤100,000,b的长度 M≤25,000,数字的大小 K≤25. 解法:[思考]1.X 暴力. ...
- kmp匹配详解
字符串算法都是毒瘤的 一.kmp算法的用处 在文本串中查找模式串的位置,数量 文本串:要在这个字符串查找模式串 模式串:在文本串中查找的字符串 全是废话 二.kmp算法的思想 话说kmp好像是3个发明 ...
- hdu 1711 KMP算法模板题
题意:给你两个串,问你第二个串是从第一个串的什么位置開始全然匹配的? kmp裸题,复杂度O(n+m). 当一个字符串以0为起始下标时.next[i]能够描写叙述为"不为自身的最大首尾反复子串 ...
- KMP(模板)
算法讲解: KMP算法最浅显易懂 模板来源: 从头到尾彻底理解KMP 首先:KMP的模板为: void get_next(char *a, int *nex) { nex[] = ; , j = ; ...
- BNUOJ-26580 Software Bugs KMP匹配,维护
题目链接:http://www.bnuoj.com/bnuoj/problem_show.php?pid=26580 题意:给一个模式串,然后m个匹配串,要求删掉匹配串中的所有存在的模式串,使得余下的 ...
- 字符串截取模板 && POJ 3450、3080 ( 暴力枚举子串 && KMP匹配 )
//截取字符串 ch 的 st~en 这一段子串返回子串的首地址 //注意用完需要根据需要最后free()掉 char* substring(char* ch,int st,int en) { ; c ...
- KMP算法模板&&扩展
很不错的学习链接:https://blog.csdn.net/v_july_v/article/details/7041827 具体思路就看上面的链接就行了,这里只放几个常用的模板 问题描述: 给出字 ...
- POJ 3080 Blue Jeans 找最长公共子串(暴力模拟+KMP匹配)
Blue Jeans Time Limit: 1000MS Memory Limit: 65536K Total Submissions: 20966 Accepted: 9279 Descr ...
- 【字符串】跳来跳去的KMP匹配
原理: 不给予证明啦(懒得一批 但是代码中有给还算详细的注释 参考:https://www.cnblogs.com/yjiyjige/p/3263858.html 模板题: 洛谷P3375: http ...
随机推荐
- centos 7.4 安装docker 19.03.6 版本。附带离线安装包
说明: 1.此环境为未安装过docker服务的环境, 如果已经安装,则自行卸载. 2.以下环境中上传的包及离线yum源默认为/home目录下,如无特殊说明,以此目录为准 步骤一:下载docker离线安 ...
- HttpServletRequest 的常用属性说明
HttpServletRequest 的常用属性总是被窝遗忘,人老了记性就不好.所以做个笔记,方便以后查看. 测试地址:http://127.0.0.1:8080/Test/test getConte ...
- 大数据高可用集群环境安装与配置(03)——设置SSH免密登录
Hadoop的NameNode需要启动集群中所有机器的Hadoop守护进程,这个过程需要通过SSH登录来实现 Hadoop并没有提供SSH输入密码登录的形式,因此,为了能够顺利登录每台机器,需要将所有 ...
- 阿里巴巴的26款Java开源项目,赶紧戳…
本人免费整理了Java高级资料,涵盖了Java.Redis.MongoDB.MySQL.Zookeeper.Spring Cloud.Dubbo高并发分布式等教程,一共30G,需要自己领取.传送门:h ...
- F - Moving Points树状数组
题:https://codeforces.com/contest/1311/problem/F 题意:给定x轴上的点以及他们的速度v,只在x轴上运动,求最小的dis之和,注意,这里的时间是可随意的,比 ...
- UML-迭代3-中级主题
初始阶段和迭代1:揭示了大量面向对象分析和设计建模的基础知识. 迭代2:特别强调对象设计模式 迭代3:涉及主题比较宽泛: 1).更多GoF设计模式及其在框架(尤其是一个持久化框架)的设计中的应用. 2 ...
- 5. react 基础 - 组件拆分 和 组件传值
1.将 todoList 进行拆分 创建 编写TodoList.js import React, {Component, Fragment} from 'react';import TodoItem ...
- Java 二维数组,排序、切换顺序,查表法二进制十进制,这班查找、排序(冒泡、选择)、遍历,获取最大小值(4)
Java 二维数组,排序.切换顺序,查表法二进制十进制,折半查找.排序(冒泡.选择).遍历,获取最大小值(4)
- How to get AutoCAD Mtext content
#region 提取一个图层上的各类元素 [CommandMethod("BlockInLayerCAD")] public void BlockInLayerCAD() { Do ...
- 【网易官方】极客战记(codecombat)攻略-森林-流星雨star-shower
流星雨不仅是一个了不起的现象,而且是获得一些钱的好机会. 简介 流星雨正在下着你的宝石和硬币! 但星形金属不是很长寿,硬币很快就消失了. 宝石不会消失. 使用或语句提取密切的金币或宝石: if ite ...