KMP算法(字符串匹配问题)acm寒假集训日记22/1/19
首先,先看一道例题:
如果不考虑超时的话,我们完全可以用最朴素的方法(暴力)去求
//暴力算法(n*m)
int ViolentMatch(char *s,char *p)
{int sLen = strlen(s);int pLen = strlen(p);int i = 0;int j = 0;while(i<sLen&&j<pLen){if(s[i]==p[j]){i++;j++;}else{i = i-j+1;j = 0;}}if(j==pLen)return i-j;elsereturn -1;
}
但n*m的时间复杂度显然是不行的(1e7~1e8——》1s)
这就引进了一个问题:如何优化?
我们不难发现:朴素的算法最浪费时间的是——》一个一个回溯的过程(找到不匹配的,j = 0,i = i-j+1)。简单来说就是i不断的回溯,很多情况下是没有意义的(人脑一眼就可以知道下一个还是不匹配,但朴素算法还是笨蛋的不断回溯)
可以看下面的例子:(人脑一眼就知道b不匹配a,希望下一步到a匹配a然后比较下一个是否匹配)
ababbba
abb
发现b与a不匹配
下一步:朴素:
ababbbaabb
我们希望:
ababbbaabb
当然也不一定这么巧,下一步就直接是答案了。
知道了优化方案,接下来就是把理想转换为现实!!!
KMP思想:
朴素算法里主串的i是不断回溯的,但从上面的例子可以看出这种回溯是没有必要的,好马不吃回头草,KMP算法就是为了避免这种没有必要的回溯。
既然i不用回溯,那么我们就要考虑j的变化。通过上面的例子我们可以知道,P串手字符和后面字符比较,如果有相等的字符,j的变化就会不同,也就是说,j的变化和主串没有关系,关键取决于p串结构是否有重复,这种字符前后重复的长度成为前缀后缀和,只要求出最长前缀后缀和就知道j怎么变化了。
这也引出了一个问题:如何求前缀后缀 ???
寻找最长前缀后缀(一大难点)
A B C A B X 0 0 0 1 2 0
模式串的各个字符 前缀 后缀 最长前缀后缀 A NULL NULL 0 AB A B 0 ABC A,AB C,BC 0 ABCA A,AB,ABC A,CA,BCA 1 ABCAB A,AB,ABC,ABCA B,AB,CAB,BCAB 2 ABCABX A,AB,ABC,ABCA,ABCAB X,BX,ABX,CABX,BCABX 0 失配时,模式串向右移动的位数为:已匹配字符数 - 失匹配字符的上一位字符所对应的最长前缀后缀和。
我们发现,当匹配到一个字符失配时,其实没有必要考虑当前失配的字符,更何况我们每次失配时,都是看的失配字符的上一位字符对应的最大长度值。如此,便引入了next数组。
给定字符串“ABCDABD”,可求得它的next数组如下:
模式串 A B C D A B D 最大长度值 0 0 0 0 1 2 0 next数组 -1(约定) 0 0 0 0 1 2 s[i] != p[j] i 不变 j = 0~j-1的最长前缀后缀 也就是:j = next[j]
规律总结:
发生失配时:
*如果next[j] != 0 则 j = next[j];
*否则下一次匹配j从0开始,意味着p串从头开始匹配
*次意味着什么呢?究其本质,next[j] = k 代表p[j] 之前的模式串子串中,有长度为 k 的相同前缀和后缀。有了这个 next 数组,在KMP匹配中,当匹配串中 j 处的字符失配时,下一步用next[j] 处的字符继续跟文本串匹配,相当于模式串向右移动j - next[j] 位。
这就是KMP的主干部分,剩下的难点就是如何求 next 数组了
KMP的算法流程
假设现在文本串 s 匹配到 i 位置,模式串p匹配到 j 位置;
*如果s[i] == p[j] ,令i++ , j++ , 继续匹配下一个字符;
*否则s[i] != p[j] ,令i不变, j = next[j];
*特殊情况:
若 j 回退到下标0的字符依旧失配,说明以当前的 i 开头的s串无法与 p 串配对,所以要将 i+1 ,从下一个开始尝试配对。为了统一和代码的简便,通常设 next[0] = -1,同时更改判断条件,完整的KMP流程如下:
流程: 初始化:i = 0,j = 0循环条件:i < slen && j < plen循环内部: 如果 j == -1 || s[i] == p[j] ,则 i++ , j++ 否则 j = next[j]循环外部: j == m 说明找到子串 j != m 说明匹配失败
KMP的时间复杂度:o(n+m)
获得next数组的代码:
void getnext()
{int n = strlen(p);Next[0] = -1;int k = -1,j = 0;while(j<n){if(k==-1||p[j]==p[k])Next[++j] = ++k;//next[j+1] = k+1;elsek = Next[k];//k小于j,所以一直是回退 }
}
KMP代码:
int kmp()
{int n = strlen(s),m = strlen(p);int i = 0,j = 0;while(i<n&&j<m){if(j==-1||s[i]==p[j]){i++;j++;}elsej = Next[j];}if(j==m) return i-j;//下表从0开始 return -1;
}
完整代码:
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
const int N = 1e6+9;
char s[N],p[N];
int Next[N],ans[N];
void getnext()
{int n = strlen(p);Next[0] = -1;int k = -1,j = 0;while(j<n){if(k==-1||p[j]==p[k])Next[++j] = ++k;//next[j+1] = k+1;elsek = Next[k];//k小于j,所以一直是回退 }
}
int kmp()
{int n = strlen(s),m = strlen(p);int i = 0,j = 0;while(i<n&&j<m){if(j==-1||s[i]==p[j]){i++;j++;}elsej = Next[j];}if(j==m) return i-j;//下表从0开始 return -1;
}
int main()
{int round = 0;int n;while(~scanf("%d",&n)&&n){scanf("%s",p);round++;getnext();printf("Test case #%d\n",round);for(int i = 1;i<=n;i++){if(Next[i]&&i%(i-Next[i])==0)printf("%d %d\n",i,i/(i-Next[i]));}printf("\n");}
}
KMP应用——最小循环节
kmp应用:
定理:假设 s 的长度为 len ,这 s 存在最小循环节,循环节的长度 L 为 len - next[len],子串为 s[0...len-next[len]-1]。
- 如果 len 可以被 len - next[len] 整除,则表明字符串 s 可以完全由循环节循环而成,循环周期 T = len/L。
- 如果不能,说明还需要再添加几个字母才能补全。需要补的个数是循环个数 L - len%L L = len - next[len]。
理解:
对于一个字符串,如abcd abcd abcd ,由长度为4的字符串abcd重复3次得到,那么必然有原字符串的前八位等于后八位。
也就是说,对于某个字符串 s ,长度为len,由长度为 L 的字符串 s 重复 R 次得到,当R>=2 时必然有 s[0...len-L-1] = s[L...len-1],字符串下标从0开始
那么对于KMP算法来说,就有next[len] = len-L。此时L肯定已经是最小的了(因为next的值是前缀和后缀相等的最大长度,即 len-L 是最大的,那么在 len 已经确定的情况下,L是最小的)。
例题:Power Strings
Given two strings a and b we define a*b to be their concatenation. For example, if a = "abc" and b = "def" then a*b = "abcdef". If we think of concatenation as multiplication, exponentiation by a non-negative integer is defined in the normal way: a^0 = "" (the empty string) and a^(n+1) = a*(a^n).
Input
Each test case is a line of input representing s, a string of printable characters. The length of s will be at least 1 and will not exceed 1 million characters. A line containing a period follows the last test case.
Output
For each s you should print the largest n such that s = a^n for some string a.
Sample Input
abcd aaaa ababab .Sample Output
1 4 3Hint
This problem has huge input, use scanf instead of cin to avoid time limit exceed.
AC代码如下:
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
const int N = 1e6+9;
char s[N],p[N];
int Next[N];
void getnext()
{int n = strlen(p);Next[0] = -1;int k = -1,j = 0;while(j<n){if(k==-1||p[j]==p[k])Next[++j] = ++k;//next[j+1] = k+1;elsek = Next[k];//k小于j,所以一直是回退 }
}int main()
{while(scanf("%s",p),p[0]!='.'){getnext();//最小循环节 int len = strlen(p);int L = len - Next[len];if(len%L==0)printf("%d\n",len/L);elseprintf("1\n");}
}
最后,感谢您的阅读!!!
ps:文章内容部分来自KMP算法详解,看完必会!_哔哩哔哩_bilibili视频
若有侵权请联系我,我将积极配合删除文章
这个视频对我有很大的帮助,同时我也非常推荐初学KMP算法的小伙伴们观看!!!
只要还有一口气,就继续战斗,继续呼吸,一直呼吸。——《荒野猎人》
KMP算法(字符串匹配问题)acm寒假集训日记22/1/19相关推荐
- 最小生成树(Kruskal算法+Prim算法)简单讲解+最小生成树例题 acm寒假集训日记22/1/8
算法简讲部分: Kruskal算法: 基于贪心策略大致过程分为第三步:1. 我们先用结构体把每条边的端点和权值记录下来,然后对每条边按权值进行排序2. 因为 使图连通最少需要n-1 条边,所以我们依次 ...
- 扩展欧几里得exgcd算法 acm寒假集训日记22/1/12
数学证明 ax + by = gcd(a,b) 令gcd(a,b) = d 得:ax + by = d ① 因为:gcd(a,b) = gcd(b,a%b) 所以:bx + a%b * y = d b ...
- 最少换乘(最短路+恶心的输入)acm寒假集训日记22/1/3 or 22/1/4
题目如下: AC代码如下: #include<iostream> #include<cstring> #include<cstdio> using namespac ...
- 【初学线段树,看这篇文章准没错】线段树(单点修改and区间修改)acm寒假集训日记22/1/10
线段树 线段树是算法竞赛中常用的用来维护区间信息的数据结构.是一名ACMer 需要掌握的一种基础.重要的数据结构线段树可以在O(logN)的时间复杂度内实现单点修改,区间修改,区间查询(区间求和,区间 ...
- spfa(STL写法)简单讲解+最短路(spfa)例题 acm寒假集训日记22/1/7
前言:SPFA(Shortest Path Faster Algorithm)算法是求单源最短路线的一种算法,它是Bellman-ford的队列优化,它是一种十分高效的最短路算法. spfa 其实可以 ...
- 矩阵问题入门(矩阵乘法and矩阵快速幂)acm寒假集训日记22/1/15
今天凌晨3点才睡,没想到通过看小说抑制玩游戏,反而看小说的时间更长. u1s1:那小说太刺激了,晚上看很有感觉,风吹草动我就会猛地看过去(类似茄子说柜子动了,哈哈),真TM(语气词)练胆量!!!..Q ...
- G - A Bug‘s Life(并查集) acm寒假集训日记22/1/2
题目如下: 提示:输入很大,你忍一下,小心超时 AC代码如下: #include<iostream> #include<algorithm> #include<cstri ...
- 逛画展(单调队列) acm寒假集训日记22/1/1
题目如下: AC代码如下: #include<iostream> #include<cstdio> #include<algorithm> #include< ...
- C++KMP算法字符串匹配(附完整源码)
C++KMP算法字符串匹配 C++KMP算法字符串匹配完整源码(定义,实现,main函数测试) C++KMP算法字符串匹配完整源码(定义,实现,main函数测试) #include <iostr ...
最新文章
- android 获取短信验证码倒计时
- [Medical Image Process] 3.2 GrayScale Morphology(灰阶图像形态学及基本运算)
- android 自定义打包,android 自定义打包后的app名称
- python学习-异常(异常类型,异常处理、自定义异常)
- 怎么安装红旗Linux5,如何用硬盘安装红旗LINUX5_0.doc
- mysql 自动生成mapper_自动生成实体类、Mapper、Mapper.xml文件
- layui表格固定列覆盖滚动条导致错位
- 数据写入磁盘的过程,咔咔的!
- 为什么换工作?(面试必问问题)
- mac Zip 常用命令
- iOS UI自动化测试详解
- Consul删除服务
- 南方cass快捷键命令修改在哪_南方CASS快捷命令
- 五种对称加密算法总结
- 使用jqery模拟网易严选购物车功能
- javaFX 学习之 超链接(HyperLink) 转载
- c 朗读html,朗读《送杜少府之任蜀》
- photoshop保存哪种格式的图像最清晰
- python html跨平台尝试Eel
- html5学生dw网页设计大作业,hbuilder仿茅台集团网页设计成品模板,图片轮播网页设计模板