import java.util.Arrays;
import java.util.Scanner;
public class A026_字符串匹配_KMP {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String str = sc.next();
String s = sc.next();
int[] next = new int[s.length()];
f(next, s);
System.out.println(Arrays.toString(next));
int i = 0;
int j = 0;
while (i < str.length()) {
if (j < 0 || str.charAt(i) == s.charAt(j)) {
j++;
i++;
if (j == s.length()) {
System.out.println(i - j);
j = next[j - 1];
i--;
}
} else {
j = next[j];
}
}
}
public static void f(int[] next, String s) {
next[0] = -1;
next[1] = 0;
char[] c = s.toCharArray();
int j = 1;
int k = next[j];
while (j < c.length - 1) {
if (k < 0 || c[j] == c[k]) {
next[++j] = ++k;
} else {
k = next[k];
}
}
}
}
KMP分两步:
第一步是求next数组,第二步是与原串进行匹配;
一、求next数组
首先看下表👇要求的串的next数组👉bababb
串 | 下标 j | 值 k | 式子 |
---|---|---|---|
b | 0 | -1 | next[1] = -1 +1 |
ba | 1 | 0 | next[2] = -1 + 1 |
bab | 2 | 0 | next[3] = 0 + 1 |
baba | 3 | 1 | next[4] = 1 + 1 |
babab | 4 | 2 | next[5] = 2 + 1 |
bababb | 5 | 3 | / |
- 首先解释最左边的那一列,我们不是建了个数组嘛,其实只要看每行最后一个值就好了,也就是加粗的那些,在进行next数组的求值中,只要比较这些就行。
- 下标这一列想必不用解释,它不会回退,只会前进,但要注意的是while循环的下标i是到len-1为止,这也是式子中的最后一行是“/”的原因,但是这个位置是会有值的,这个在之后式子这一列说。
- 值 这一列很奇特,k既是next数组的值,又是下标,感觉这里有点类似于动态规划。
- 式子这一列中,我们首先固定两个值,分别是next数组下标为0和1的位置,值分别是-1和0,为什么是-1呢?这个到下一步main函数的while里说明
- 接着说next数组的创建,如果下标k和下标j所对应的c数组中的值相等,那么就将之前的k的值++那么对应的,我们看第一第二行,第一个是b,第二个是a,这两个是不想等的,那么就执行else里面的k = next[k];那么k现在的值就是-1了,那么再进行while循环,这时候,因为k<0,那就会进到if里面,这时候,j变成了2,然后它的值就是0,因为-1±1;
- 当j=2时,也就是延续上面的走下来,k这个时候是0,但是b和bab,这两个的末位是相等的,那么就将next数组下标为3的这个值,变为++k,也就是1,对应上表,那么接下来也是以此类推,值得注意的就是,他每一次算的,都是下一个下标的值,所以这里会看到,式子这一列的最后一行是空的,但是是有值的。
- 这样next数组就创建完了,那么接下去就进入主函数了👇
二、进行匹配
- 首先就是两个下标了i和j
- 然后这里的i是不会往回走的,只会向前,j的值呢,就是j=next[j],但是这里是什么意思呢?j一直是下标,没变,取出来的值也是下标,这里下标的含义就是说:匹配到这个值的前面,哪个下标是已经相等的了,不用再走的了,比如这里的匹配串中的重复值,就是bababb bababb注意我加粗的两个部分,这两个就是重复的,也就是说,这两个串不用进行比较,下次直接从j的下标3开始比较即可。
- 那么我们不是有个下标是-1嘛,我们再看原串,在下标为7的位置,有个c,那么走到这里的话,j的下标就是-1了,因为两个元素不相等,我们也可以把所有走的i和j都打印出来看👇,看下面第三个箭头指得位置
那么前两个箭头得值为什么是从大到小的呢?明明没有i变小的地方呀,其实这里,就是next数组的功劳了,j = next[j];