首先,先看一道例题:

如果不考虑超时的话,我们完全可以用最朴素的方法(暴力)去求

//暴力算法(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]。

  1. 如果 len 可以被 len - next[len] 整除,则表明字符串 s 可以完全由循环节循环而成,循环周期 T = len/L。
  2. 如果不能,说明还需要再添加几个字母才能补全。需要补的个数是循环个数                 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
3

Hint

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相关推荐

  1. 最小生成树(Kruskal算法+Prim算法)简单讲解+最小生成树例题 acm寒假集训日记22/1/8

    算法简讲部分: Kruskal算法: 基于贪心策略大致过程分为第三步:1. 我们先用结构体把每条边的端点和权值记录下来,然后对每条边按权值进行排序2. 因为 使图连通最少需要n-1 条边,所以我们依次 ...

  2. 扩展欧几里得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 ...

  3. 最少换乘(最短路+恶心的输入)acm寒假集训日记22/1/3 or 22/1/4

    题目如下: AC代码如下: #include<iostream> #include<cstring> #include<cstdio> using namespac ...

  4. 【初学线段树,看这篇文章准没错】线段树(单点修改and区间修改)acm寒假集训日记22/1/10

    线段树 线段树是算法竞赛中常用的用来维护区间信息的数据结构.是一名ACMer 需要掌握的一种基础.重要的数据结构线段树可以在O(logN)的时间复杂度内实现单点修改,区间修改,区间查询(区间求和,区间 ...

  5. spfa(STL写法)简单讲解+最短路(spfa)例题 acm寒假集训日记22/1/7

    前言:SPFA(Shortest Path Faster Algorithm)算法是求单源最短路线的一种算法,它是Bellman-ford的队列优化,它是一种十分高效的最短路算法. spfa 其实可以 ...

  6. 矩阵问题入门(矩阵乘法and矩阵快速幂)acm寒假集训日记22/1/15

    今天凌晨3点才睡,没想到通过看小说抑制玩游戏,反而看小说的时间更长. u1s1:那小说太刺激了,晚上看很有感觉,风吹草动我就会猛地看过去(类似茄子说柜子动了,哈哈),真TM(语气词)练胆量!!!..Q ...

  7. G - A Bug‘s Life(并查集) acm寒假集训日记22/1/2

    题目如下: 提示:输入很大,你忍一下,小心超时 AC代码如下: #include<iostream> #include<algorithm> #include<cstri ...

  8. 逛画展(单调队列) acm寒假集训日记22/1/1

    题目如下: AC代码如下: #include<iostream> #include<cstdio> #include<algorithm> #include< ...

  9. C++KMP算法字符串匹配(附完整源码)

    C++KMP算法字符串匹配 C++KMP算法字符串匹配完整源码(定义,实现,main函数测试) C++KMP算法字符串匹配完整源码(定义,实现,main函数测试) #include <iostr ...

最新文章

  1. android 获取短信验证码倒计时
  2. [Medical Image Process] 3.2 GrayScale Morphology(灰阶图像形态学及基本运算)
  3. android 自定义打包,android 自定义打包后的app名称
  4. python学习-异常(异常类型,异常处理、自定义异常)
  5. 怎么安装红旗Linux5,如何用硬盘安装红旗LINUX5_0.doc
  6. mysql 自动生成mapper_自动生成实体类、Mapper、Mapper.xml文件
  7. layui表格固定列覆盖滚动条导致错位
  8. 数据写入磁盘的过程,咔咔的!
  9. 为什么换工作?(面试必问问题)
  10. mac Zip 常用命令
  11. iOS UI自动化测试详解
  12. Consul删除服务
  13. 南方cass快捷键命令修改在哪_南方CASS快捷命令
  14. 五种对称加密算法总结
  15. 使用jqery模拟网易严选购物车功能
  16. javaFX 学习之 超链接(HyperLink) 转载
  17. c 朗读html,朗读《送杜少府之任蜀》
  18. photoshop保存哪种格式的图像最清晰
  19. python html跨平台尝试Eel
  20. html5学生dw网页设计大作业,hbuilder仿茅台集团网页设计成品模板,图片轮播网页设计模板

热门文章

  1. 初步学习C#网络编程
  2. java做校园一卡通技术_基于JAVACARD校园一卡通设计及实现.doc
  3. 应用于新型汽车的多种TDK传感器
  4. JAVA中Bean是什么?
  5. CodeReview常见代码问题
  6. 现代人的职业枯竭与心理健康
  7. 微信小程序调试页面的坑
  8. Python 模拟浏览器 POST请求思路
  9. Modulated Variational auto-Encoders for many-to-many musical timbre transfer论文阅读
  10. Reddit重写其iOS应用,改进性能、模块化和测试