算法学习之KMP(模板整理)
来源:互联网 发布:linux获取线程优先级 编辑:程序博客网 时间:2024/05/16 07:28
一、学习记录
犹记当年学习C语言的字符串的时候那个悲情。。。
于是组队之后果断声明:鶸不玩字符串
然后将字符串丢给队友。。。
后来觉得吧,一些基础的东西还是要会的,比如KMP
粗浅的学了下KMP算法,也不敢瞎说什么,主要整理了下模板
学习记录:
主要看了刘大大的蓝书《算法竞赛入门经典--训练指南》
还有几篇博客:
1. KMP算法详解
2.KMP算法详解
3.KMP算法的一些题目
二、两种模板
初期使用的模板:
#define mem(a,x) memset(a,x,sizeof(a))#include<bits/stdc++.h>using namespace std;typedef long long ll;const int N = 100000;/* KMP 算法模板 时间复杂度 : O(n+m) [其中 n,m 是两字符串的长度] 说明: 1. a是长串 s是短串 即 a.size >= s.size 2.实现了“查找第一次匹配的位置” “匹配的次数” “记录所有匹配区间”三个功能 3.三个功能的实现代码非常相似,第3个功能可以同时实现前2个的功能*/struct KMP{ int f[N + 7]; void getfill (string s) //预处理 { memset (f, 0, sizeof (f) ); //根据其前一个字母得到 for (int i = 1; i < s.size(); i++) { int j = f[i]; while (j && s[i] != s[j]) j = f[j]; f[i + 1] = (s[i] == s[j]) ? j + 1 : 0; } } int finds (string a, string s) //找到第一次匹配的位置 a.size >= s.size { getfill (s); int j = 0; for (int i = 0; i < a.size(); i++) { while (j && a[i] != s[j]) j = f[j]; if (a[i] == s[j]) j++; if (j == s.size() ) { return i - s.size() + 1; //返回小串在大串中第一次出现的位置 } } return -1;//不匹配返回-1 } int findtimes(string a,string s)//返回匹配次数 { getfill(s); int j = 0;int times = 0; for (int i = 0;i < a.size();++i) { while (j&&a[i] != s[j]) j = f[j]; if (a[i] ==s[j]) j++; if (j == s.size()) { times++; } } return times;//不匹配的times将是0 } struct Interval { int l,r; }q[N];//记录所有匹配区间 [指在长串中的下标,下标从0开始] int t;//记录匹配次数 void findinterval(string a,string s)//找到并记录所有的匹配区间 { getfill(s); int j = 0;t = 0; for (int i = 0;i < a.size();++i) { while (j&&a[i]!=s[j]) j = f[j]; if (a[i] == s[j]) j++; if (j == s.size()) { t++; q[t].l = i - s.size() + 1; q[t].r = i; } } } void Print()//第3种功能附带打印功能 { cout<<"两串匹配了 "<< t <<"次,分别是 :"<<endl; for (int i = 1;i <= t;++i) { cout<<"第"<<i<<"次 : ["<<q[i].l<<","<<q[i].r<<"]\n"; } }}kmp;int main(){ string s,a; while (cin >> a >> s) { kmp.findinterval(a,s); kmp.Print(); } return 0;}
后期的模板:
#define mem(a,x) memset(a,x,sizeof(a))#include<bits/stdc++.h>using namespace std;typedef long long ll;const int N = 1000007;struct KMP{ string s, t; //s.size > t.size int slen, tlen; int nx[N]; KMP (string s = "", string t = "") : s (s), t (t) //构造函数 { slen = s.size(); tlen = t.size(); mem (nx, 0); } void init (string s, string t) { this->s = s; this->t = t; slen = s.size(); tlen = t.size(); mem (nx, 0); } void getnx() { int i = 0, k = -1; nx[0] = -1; while (i < tlen) { if (k == -1 || t[i] == t[k]) nx[++i] = ++k; else k = nx[k]; } } int findindex() //返回首次出现的位置 { int i = 0, j = 0; getnx(); while (i < slen&&j < tlen) { if (j == -1||s[i] == t[j]) ++i,++j; else j = nx[j]; } if (j == tlen) return i - tlen; else return -1;//不匹配 } int findtimes()//返回出现次数 { if (slen == 1&&tlen == 1) { return s[0] == t[0]; } getnx(); int times = 0; for (int i = 0,j = 0;i < slen;++i) { while (j > 0&&s[i]!=s[j]) j = nx[j]; if (s[i] == t[j]) ++j; if (j == tlen) { ++times; j = nx[j]; } } return times; }};int main(){ return 0;}
三、关于next的感受心得
个人以为,KMP的精髓就在伟大的getnext的预处理里,(或者说在神奇的next数组里)
getnext是对模式串进行预处理,而完全不涉及主串
getnext中其实是字符串自己和自己匹配,
关于next数组,他的意义不限于KMP算法,他可以找到字符串的循环节,他有很多独特的性质(很有用)
这里随意测试了2个性质
#define mem(a,x) memset(a,x,sizeof(a))#include<iostream>#include<cstdio>#include<cstring>#include<algorithm>#include<queue>#include<set>#include<stack>#include<cmath>#include<map>#include<stdlib.h>#include<cctype>#include<string>#define Sint(n) scanf("%d",&n)#define Sll(n) scanf("%lld",&n)#define Schar(n) scanf("%c",&n)#define Sint2(x,y) scanf("%d %d",&x,&y)#define Sll2(x,y) scanf("%lld %lld",&x,&y)#define Pint(x) printf("%d",x)#define Pll(x) printf("%lld",x)#define Pintl(x) printf("%d\n",x)using namespace std;typedef long long ll;const int N = 1000007;int nx[N];char s[N];void getnx(){ int len = strlen(s); int i = 0,k = -1; nx[0] = -1; while (i < len) { if (k == -1||s[i] == s[k]) nx[++i] = ++k; else k = nx[k]; }}/* 结论 1 : 如果 len-next[len] 能被 len 整除 则 len - next[len] 是该串的循环节*//*int main(){ while (~scanf("%s",s)) { if (s[0] == '.') break; getnx(); int len = strlen(s); if (len%(len-nx[len]) == 0) { //Pintl((len-nx[len]));//所以这个是循环节的长度 Pintl(len/(len-nx[len]));//这个是循环节的次数 } else puts("1"); } return 0;}*//* 结论 2 : s[0] ~ s[next[len]-1] 中的内容一定能与 s[len-next[len]] ~ s[len-1] 匹配*/int main(){ while (~scanf("%s",s)) { getnx(); int len = strlen(s); for (int i = 0;i < nx[len];++i) { putchar(s[i]); } cout<<endl; for (int i = len-nx[len];i < len;++i) { putchar(s[i]); } cout<<endl; }}
/* 结论 3 : s[0] ~ s[next[i]-1] 中的内容一定能与 s[i-next[i]] ~ s[i-1] 匹配*/
四、针对next的应用
裸的KMP也有几道入门题,HDU 1711 POJ 3461(直接套模板即可)
另外关于next的性质的两道题:
POJ 2406
利用next数组找字符串的循环节
#define mem(a,x) memset(a,x,sizeof(a))#include<iostream>#include<cstdio>#include<cstring>#include<algorithm>#include<queue>#include<set>#include<stack>#include<cmath>#include<map>#include<stdlib.h>#include<cctype>#include<string>#define Sint(n) scanf("%d",&n)#define Sll(n) scanf("%lld",&n)#define Schar(n) scanf("%c",&n)#define Sint2(x,y) scanf("%d %d",&x,&y)#define Sll2(x,y) scanf("%lld %lld",&x,&y)#define Pint(x) printf("%d",x)#define Pll(x) printf("%lld",x)#define Pintl(x) printf("%d\n",x)using namespace std;typedef long long ll;/* 结论:如果有一个字符串s,长度是len,它的失败函数是next, 如果len能被len-next[len]整除, 那么len-next[len]就是我们要求的那个子串的长度, 与之对应的字符串,就是我们想得到的子串*/const int N = 1000007;int nx[N];char s[N];void getnx(){ int len = strlen(s); int i = 0,k = -1; nx[0] = -1; while (i < len) { if (k == -1||s[i] == s[k]) nx[++i] = ++k; else k = nx[k]; }}int main(){ while (~scanf("%s",s)) { if (s[0] == '.') break; getnx(); int len = strlen(s); if (len%(len-nx[len]) == 0) { //Pintl((len-nx[len]));//所以这个是循环节的长度 Pintl(len/(len-nx[len]));//这个是循环节的次数 } else puts("1"); } return 0;}
POJ 2752
又是利用了next数组的性质
#define mem(a,x) memset(a,x,sizeof(a))#include<iostream>#include<cstdio>#include<cstring>#include<algorithm>#include<queue>#include<set>#include<stack>#include<cmath>#include<map>#include<stdlib.h>#include<cctype>#include<string>#define Sint(n) scanf("%d",&n)#define Sll(n) scanf("%lld",&n)#define Schar(n) scanf("%c",&n)#define Sint2(x,y) scanf("%d %d",&x,&y)#define Sll2(x,y) scanf("%lld %lld",&x,&y)#define Pint(x) printf("%d",x)#define Pll(x) printf("%lld",x)#define Pintl(x) printf("%d\n",x)using namespace std;typedef long long ll;/* 从n - 1位既最后一位开始回滚, 若s[next[n-1]] == s[n-1], 则子串s[0,1,2,...,next[n-1]]是满足条件的子串。 然后判断s[next[next[n-1]]] == s[n-1]是否成立, 这样一直回滚,直到next[next[.....next[n-1]]] == -1为止。*/const int N = 400007;int nx[N];char s[N];int len;void getnx(){ int i = 0,k = -1; nx[0] = -1; while (i < len) { if (k == -1||s[i] == s[k]) nx[++i] = ++k; else k = nx[k]; }}int main(){ while(~scanf("%s",s)) { len = strlen(s); getnx(); int p = nx[len-1]; vector<int>r; while (p+1) { if (s[p] == s[len-1]) r.push_back(p+1); p = nx[p]; } for(int i = r.size()-1;i >= 0;--i) { Pint(r[i]);putchar(' '); } Pintl(len); } return 0;}
五、总结
1.KMP算法本身可能并没有特别的吸引我,但是那个神奇的next数组再次让本鶸见识到算法的伟大之处
鶸看别人写好的文章都不是很懂,简直不造那些前人是怎么想出来这么精妙的方法的(膜拜~~~~)
2.字符串的算法不仅仅限于字符串,在做一些关于数列的题的时候也可能用到字符串的算法
3.据说KMP和Trie过了就是AC自动机了,想当初第一次听到这个名字的时候还以为AC自动机
是指可以在比赛中自动AC(Accepted)的算法。。。囧~~~
- 算法学习之KMP(模板整理)
- 模板整理之KMP
- KMP 算法 学习 整理
- KMP算法(模板)
- 【KMP】KMP算法模板
- 字符串单模板匹配学习笔记(一)kmp算法
- KMP算法(各种模板)
- 自我学习之KMP算法
- KMP算法自己整理(精简版)
- kmp算法整理
- 经典KMP算法整理
- Number Sequence (KMP算法(模板))
- hihocoder #1015 : KMP算法 (模板)
- HDU2087(KMP算法模板题)
- HDU1711(KMP算法模板题)
- kmp算法模板
- kmp算法模板
- KMP算法模板
- 动态代理的实现
- hdoj 5631 Rikka with Graph 【并查集】
- 数据结构----左偏树
- 大数的四则运算
- HDU 2639 Bone Collector II [动态规划 第k大01背包]
- 算法学习之KMP(模板整理)
- c++之String类的尝试版本
- hdu 5326(Work)
- 论文:Distilling the Knowledge in a Neural Network
- AngularJS 包含
- centos 安装 lnmp
- 改变从此刻开始
- PAT 1100. Mars Numbers (20)
- Cf 364div2 D[数学公式推导]