kmp是最早接触的字符串匹配算法,是一种能够快速在文本串中快速找到目标串的算法。由于接触的到的版本有点多,这里做一下对kmp算法的一点整理。
主要参考博客: pecco的算法笔记:https://zhuanlan.zhihu.com/p/105629613和天勤的数据结构书
首先是最常见的kmp算法,这里求出的next数组其实是下一个模式匹配的位置,实际上也是位置i与前缀的最大匹配长度,即部分匹配表PMT
void get_pmt(const string& s) { for (int i = 1, j = 0; i < s.length(); ++i) { while (j && s[i] != s[j]) j = pmt[j - 1]; if (s[i] == s[j]) j++; pmt[i] = j; } for(int i=0;i<s.length();i++){ cout<<s[i]<<" "<<pmt[i]<<"\n"; } }
求出的pmt函数可以求最小循环节等问题,这是最常见的kmp。
之后就是天勤数据结构书上面的kmp:
适用于单一字符串求是否在主串中出现,被求字符串称为模式串
首先对模式串求next数组,next数组的含义是:next[i]表示主串在模式串位置s[i]失配之后,所需要跳到的下一个需要匹配的模式串位置
因此对于位置1来说,如果此时失配,则前方没有任何元素可以与它匹配,所以next[1]为0
对于位置i来说,假设此时失配,则跳转到他的next[i]位置处,next[i]位置可以保证它前面的元素都是不需要匹配的,如果next[i]已经为0
则说明前面已经没有“首尾相同的”的串了,只能从头开始
因此我们可以注意到,next[i]并不是求出的当前位置i的重合部分位置,而是当前位置i失配之后前面部分的重合位置
这个next数组并不是平常写的kmp算法当中的pmt,而只是记录下一个匹配位置的数组罢了
它忽视了最后一个元素对于整个字符串的影响
所以并没有求最小循环节的作用^^
void getnext(Str substr,int next[]){ int i=1,j=0; next[1]=0; while(i<substr.length){ if(j==0||substr.ch[i]==substr.ch[j]){ //表明此时已经发现了前面数组的最小循环位置 i++; j++; next[i]=j; } else j=next[j]; } for(int i=1;i<=substr.length;i++) cout<<substr.ch[i]<<" "<<next[i]<<"\n"; }
nextval数组由来:
由于next数组只能记录下s[i]前缀的最大重合部分,但是无法判断s[i]与s[next[i]]之间的关系,所以当两者元素值相同时,就进行了很多
无用的匹配的(一直在和相同的元素s[i]进行匹配)
所以用nextval数组来记录和s[i]元素不同的next[kn],kn表示经过n次迭代之后的next[i]
如何求nextval[i]
1、若s[i]==s[next[i]],则nextval[i]=nextval[next[i]]
证明:1)首先由于数组是从左往右推,所以nextval[next[i]]必然已经存在
2)由于s[nextval[next[i]]]!=s[next[i]],故而也不等于s[i]
2、 若s[i]!=s[next[i]],则nextval[i]=next[i]
void getnextval(Str substr,int nextval[]){ int i=1,j=0; //字符从1开始 nextval[1]=0; while(i<substr.length){ if(j==0||substr.ch[i]==substr.ch[j]){ ++i,++j; if(substr.ch[i]!=substr.ch[j]) nextval[i]=j; else nextval[i]=nextval[j]; } else j=nextval[j]; } }
这两种求kmp的方法都大同小异,PMT也同样可以用类似nextval的方法求出效率更高的数组,但是如果失去了原本的一些性质反而得不偿失,所以可能用的少一些