KMP算法中next与nextval的实现原理
最近学习KMP算法,在next数组以及nextval数组的理解上下了好大的功夫,才得以理解。
仅作为备忘以及参考作用,在此记录下理解过程。如有错漏指出,望斧正!
KMP算法的实现以及next,nextval数组的计算我就不加赘述了,以下解释个人对两个数组的理解。
前提信息
next,nextval数组是为了实现不同于朴素模式匹配算法(Brute-Force算法) 的KMP算法而添加的,其中原版KMP使用的是next数组,然而,在某些情况下(在各大牛的KMP算法介绍里应该有提到,个人认为是一些额外的运算浪费)next数组仍会出现不足之处,因此,人们就设计了nextval数组进行优化。
在以下文字中,我将使用:
主串:将在其中进行搜索的字符串。
模式串:待搜索的字符串。
即,对于master = “ababad”, mode = "baba"两个字符串,
在master中匹配mode,此时master为主串,mode为模式串。
next数组
在这里,我将假设你已经理解什么是朴素模式匹配算法,并且明白了其中原理。
next数组,又被称作next函数、覆盖函数等等,是为了以
主串不动,移动模式串的方法来实现时间复杂度的优化(O(mn) --> O(m+n))
继续使用以上的两个字符串,master = “ababacbabad”, mode = “babad”,在master中匹配mode。
对于模式串 babad,它的next数组如下
mode | b | a | b | a | d |
---|---|---|---|---|---|
next | -1 | 0 | 0 | 1 | 2 |
含义即,当在j处匹配不上时,将模式串指针指到next[j]处,
参考下图:
nextval数组
对于模式串 babad,它的next数组如下
mode | b | a | b | a | d |
---|---|---|---|---|---|
next | -1 | 0 | 0 | 1 | 2 |
它的nextval数组如下
mode | b | a | b | a | d |
---|---|---|---|---|---|
nextval | -1 | 0 | -1 | 0 | 2 |
二者唯一的区别就是,若某一位的next值对应的字符与这一位字符相同,那么这一位nextval的值则为这一位的next值对应的next值。
形式化表达:
if(mode[j] == mode[next[j]]){
nextval[j] = nextval[next[j]];
//因为迭代是从左到右进行的,此时nextval[next[j]]已经按此原则完成赋值
}
按此原则实现的原因
以上述模式串mode第三位mode[2] ‘b’ 为例,假设有一主串“bacad”,
此时显然在 j = 2处匹配失败,
按照next数组的操作方法:
①读取next[2] = 0;
②移动mode,将第0位与j对齐,进行匹配
③mode[next[2]] == mode[0] == ‘b’,重蹈覆辙。
④读取next[0] == -1;
⑤移动mode,将第-1位与i对齐。
按照nextval的操作方法:
①读取nextval[2] = next[next[2]] = -1;
②移动mode,将第-1位与i对齐。
这便是使用nextval减少额外运算的智策。
附上KMP算法代码以供参考
#include <iostream>
#include <string>
#include <iomanip>
using namespace std;
void Cal_next(int *next, string mode, int len) {
//next[i]含义为若在i处匹配失败时,i应该跳转至的模式串中的位置
int i, j;
j = -1;
i = 0;
next[0] = -1;
while(i<len-1){
if(j==-1||mode[i] == mode[j]){
i++;
j++;
next[i] = j;
}
else{
j = next[j];
}
}
//print:
cout<<"[";
for(int t = 0;t<len;t++){
cout<<setw(2)<<next[t]<<" ";
}
cout<<"]"<<endl;
}
void Cal_nextval(int *nextval, string mode, int len) {
//nextval[i]含义为若在i处匹配失败时,i应该跳转至的模式串中的位置
int i, j;
j = -1;
i = 0;
nextval[0] = -1;
while(i<len-1){
if(j==-1||mode[i] == mode[j]){
i++;
j++;
if(mode[i]==mode[j]){
nextval[i] = nextval[j];
}
else{
nextval[i] = j;
}
}
else{
j = nextval[j];
}
}
//print:
cout<<"[";
for(int t = 0;t<len;t++){
cout<<setw(2)<<nextval[t]<<" ";
}
cout<<"]"<<endl;
}
int KMP_nextval(string master, string mode) {
int l1 = master.length();
int l2 = mode.length();
int * next = new int[l2+1];
int pos = -1;
Cal_next(next, mode, l2);
//int * nextval = new int[l2+1];
//cout<<endl;
//Cal_nextval(nextval, mode, l2);
int i = 0, j = 0;
while(i<l1){
if(j==-1||master[i]==mode[j]){
i++;
j++;
if(j>=l2){
pos = i - l2;
break;
}
}
else{
j = next[j];
}
}
return pos;
}
int KMP_next(string master, string mode) {
int l1 = master.length();
int l2 = mode.length();
int pos = -1;
int * nextval = new int[l2+1];
Cal_nextval(nextval, mode, l2);
int i = 0, j = 0;
while(i<l1){
if(j==-1||master[i]==mode[j]){
i++;
j++;
if(j>=l2){
pos = i - l2;
break;
}
}
else{
j = nextval[j];
}
}
return pos;
}
希望以上内容能为看到这里的你们提供一些帮助!如有错漏指出,望斧正!
//Szp 2018.10.26