数据结构-KMP算法 速通指南
文章目录
1.KMP算法介绍
KMP算法用于模式串匹配,其代码简洁高效但思维较深。主要可以概括为基于bf暴力匹配算法通过减少回溯匹配的次数达到效率改进目的的改良算法。其难点在于掌握求next数组的方法思想。
忘记了在哪里看的,说的是KMP算法可以读成“看门牌算法”,因为next数组实际上存储的就是模式串中前一个最长匹配部分的末尾下标后移一位。比如现有模式串“abcddabc”,那么第二个abc处的next数组就是第一个abc后移一位,即第一个d的位置下标。这样就可以减少重复匹配的次数。
2.求next数组
要求next数组,首先我们就需要先创建一个next数组int next[MAXN]
,而求next数组的过程,相当于是模式串自己跟自己匹配的过程。
我们用一个动图来说明:
当图中abc串已经匹配的情况下,由于a与d并不匹配,所以k就往前到与abc匹配串的后一位开始继续匹配,这样就不需要重复匹配abc了。
void GetNext(string t,int next[]){
int j = 0,k = -1;//j为后匹配指针变量,k为前匹配指针变量
next[0] = -1;//第一个字符前面没有字符串,更没有可匹配的字符串,所以直接给-1
while(j < t.size() - 1){//string t 的数组下标最大为t.size() - 1,而每次求next数组 都是赋值的的j + 1,所以循环条件设置为j < t.size() - 1即可求得全部next数组。
if(k == -1 || t[j] == t[k]) next[++j] = ++k;
//k为-1时或者两指针变量指向的字符相等时
//两指针变量全都后移一位
else k = next[k];
//不匹配时的代码是整个算法的难点所在,理解了这里的思想才能学会KMP算法
//不匹配时我们就把前匹配指针移动到前一个相同匹配串之后的位置看 是否匹配,否则就一直往前移动(看门牌)
}
}
3.求nextval数组
在理解了next数组求法之后,求理解nextval就变得简单多了
void GetNextval(string t,int nextval[]){
int j = 0,k = -1;
nextval[0] = -1;
while(j < t.size()){
if(k == -1 || t[j] == t[k]){
if(t[++j] != t[++k]) nextval[j] = k;
else nextval[j] = nextval[k];
else k = nextval[k];
}
}
}
4.匹配主串
正如之前所说,求next数组(或者nextval数组)其实相当于模式串自己与自己匹配,那么真正的主串与模式串匹配也就与求next数组的代码高度相似。
int KMP(string s,string t){
int next[MAXN],i = 0,j = 0;//i对应主串的匹配下标,j对应模式串的匹配下标
//这里的next数组既可以是next数组也可以是nextval数组,因为用法相同
GetNext(t,next);//或者GetNextval(t,next)
while(i < s.size() && j < t.size()){
if(j == -1 || s[i] == t[j] i++,j++;//匹配则指针后移
else j = next[j];//不匹配时匹配回溯
}
if(j >= t.size()) return i-t.size();//如果子串匹配到末尾则说明已经匹配成功
else return - 1;//否则匹配失败
}
其实KMP说难也只是基本数据结构中算难,实际上还是难度比较适中的,但是对于很多人颇有只可意会不可言传的感觉,所以学习感觉会相对困难一点,而一旦学会,便会觉得其思想很深刻但实现很简单优美。