4.next数组求解算法优化
最后,来看一下上边的算法存在的缺陷。来看第一个例子:
显然,当我们上边的算法得到的next数组应该是[ -1,0,0,1 ]
所以下一步我们应该是把j移动到第1个元素咯:
不难发现,这一步是完全没有意义的。因为后面的B已经不匹配了,那前面的B也一定是不匹配的,同样的情况其实还发生在第2个元素A上。
显然,发生问题的原因在于P[j] == P[next[j]]。
补充说明:这部分作者说的也比较清楚了。实际上对下面的代码if(p[++j]==p[++k]),我们注意是先自加,再使用。所以我们按照j不变的情况下解释下一步流程就是p[j+1]==p[next[j]+1];此时比较将无意义,因为p[next[k]+1]位就已经表示,就是k+1位和主串的i不相等,要移动的j下标为next[K+1],因为p[k+1]又等于p[j+1],也就是说比较j+1位和主串的i位是否相等时,也将要j移到next[K+1]位去;
所以我们也只需要添加一个判断条件即可:
public static int[] getNext(String ps) {
char[] p = ps.toCharArray();
int[] next = new int[p.length];
next[0] = -1;
int j = 0;
int k = -1;
while (j < p.length - 1) {
if (k == -1 || p[j] == p[k]) {
if (p[++j] == p[++k]) { // 当两个字符相等时要跳过
next[j] = next[k];
} else {
next[j] = k;
}
} else {
k = next[k];
}
}
return next;
}
public static int[] getNext(String ps) {
char[] p = ps.toCharArray();
int[] next = new int[p.length];
next[0] = -1;
int j = 0;
int k = -1;
while (j < p.length - 1) {
if (k == -1 || p[j] == p[k]) {
++j,++k;
if (p[j] == p[k]) { // 当两个字符相等时要跳过
next[j] = next[k];
} else {
next[j] = k;
}
} else {
k = next[k];
}
}
return next;
}
为什么优化next数组?
上面只是给出了部分匹配的结果,从第6步往后比i=4,j=4有s[i]!=t[j]
由于next[j]的指示还要进行i=4j=3,i=4j=2,i=4j=1,i=4j=0这4次比较。
实际上,模式中0,1,2,3和第4个字符都相等,因此不需要再和主串中第4个字符比较,因而可以将模式一气向右滑动5个字符,直接进行i=5j=0时的字符
按上述定义next[j]=k,模式串p[j]==p[k],则主串字符s[i]和p[j]比较不等,则s[i]和p[next[j]]也没有必要比较(因为next[j]=k,其中p[j]==p[k]),而直接和p[next[k]]进行比较
换句话说,此时next[j]应该和next[k]相同
这都是由:
辅助理解:严蔚敏《数据结构》81页,关于kmp的两种情况(与严书不同的是,我存放数据从数组0号单元开始,严书0号单元存储长度从1号单元存储数据,这是区别要注意,所以下面的表述除数字外和严书相同)
在求得模式的next函数之后,匹配可如下进行:假设以指针i和j 分别指示主串s和模式t中正待比较的字符,令i的初值为0,j的初值为0。若在匹配过程中 ,s[i]==t [j] ,则 i 和j 分别增1,否则,i不变,而j退到next[j]的位置再比较,若相等,则指针各自增1,否则j再退到下一个next 值的位置,依次类推,直至下列两种可能:
一种是j 退到某个next 值(next [...next[j]...]])时字符比较相等,则指针各自增1,继续进行匹配;
另一种是j 退到值为-1(即模式的第一个字符“失配”,第一个字符对应数组的0号单元,所以数组0号单元失配则则退到0号单元的左边),则此时需将模式继续向右滑动一个位置,即从主串的下一个字符 s[i+1] 起和模式重新开始匹配。
while (j < p.length - 1) {
if (k == -1 || p[j] == p[k]) {
++j,++k;
if (p[j] == p[k]) { // 当两个字符相等时要跳过
next[j] = next[k];
} else {
next[j] = k;
}
} else {
k = next[k];
}
}
//为什么while (j < p.length - 1) 因为 if (k == -1 || p[j] == p[k]) {++j,++k;循环体内++j,
而数组下标0--p.length - 1,要进入循环j最大p.length - 2,里面if (k == -1 || p[j] == p[k]) {++j,++k;循环体内才能进行++j
//为什么next[j]=k
为什么next[j]=k;
为什么next[j]=next[k]?
因为if(j==-1||p[k]=p[j]),假设next[j]=k,进入++j,++k,上图红色p[k]==p[j](k++,j++后的j和k)
即next[j+1]=next[k+1]=k+1
tips:为了好理解,上面两句解释中的j和k为if(j==-1||p[k]=p[j]),
的值,
于是就有了next[j]=next[k]