1. 朴素的模式匹配算法
从主串S="goodgoogle"中找到T=“google’这个子串的位置
思路:
主串内设置指针i,子串内设置指针j,i与j同步开始向前进行比较,若出现不同,i回溯到上一次i开始位置的下一位,j回溯到0,继续比较
public static int Index(String S,String T){
int i =0;
int j =0;
char[] s =S.toCharArray();
char[] t =T.toCharArray();
while(i<s.length&&j<t.length){
if(s[i]==t[j]){
i++;
j++;
}
else{
i = i - j + 1;
j = 0;
}
}
if(j>=t.length){
return i-j;
}
else{
return 0;
}
KMP算法
思路:
利用已经部分匹配这个有效信息,保持i指针不回溯,通过修改j指针,让模式串尽量地移动到有效的位置
所以,整个KMP的重点就在于当某一个字符与主串不匹配时,我们应该知道j指针要移动到哪?
如果最前面的k个字符[0~k]和j之前的最后k个字符[j-k~j-1]是一样的,那我们直接将j指针回溯到k的位置
为此,我们要找出指针j在最后一位时,如果失配,应该回溯到哪一位,也就是k指针的位置(k的值)
我们可以通过找出最长公共前后缀的方法来求k
如上图,ABCAB的最长公共前后缀为2,所以k=2(最前面的2个字符[0~2]和j之前的最后2个字符[j-2~j-1]是一样的,并且k指针指向第2位),当j在最后一位失配时,j应该回溯到子串第2位,也就是C的位置
最长前后缀的求法
参考阮一峰老师的文章
http://www.ruanyifeng.com/blog/2013/05/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm.html
作者: 阮一峰
失配查询表(Next 表)的求法
然而,最长前后缀表(pmt表)并不是最后的失配查询表(next表),因为当我们失配时,我们要查询的是失配位前一位的最长公共前后缀
因此,我们要将pmt表整体向后移动一格,再在第0位补上-1
求next数组代码实现
现在,我们再看一下如何编程快速求得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;
next[j] = k;
} else {
k = next[k];
}
}
return next;
}
假设子串比较指针为j,若失配要回溯到指针k的位置
先看特殊情况,子串第0位j=0时,就与父串指针i所指的位置不匹配,此时的做法与朴素算法无异,将子串整体向前移动一格,再次从子串第0位开始与父串比较。因此,next[0] = -1,指的是父串指针i要后移一位
j = 1时,next[1] = 0(必定是0,因为只有一个元素,没有最长公共前后缀)
下面是最重要的:
要求j+1指针指向位置的next值,就是看j位的最大公共前后缀的长度。
求j位的最大公共前后缀的长度,则首先需要看j-1位的next值。
(1) 若j-1位的next值为0(此时k必定等于0),那么j所在位置只需要与子串的第0位比较(因为前一位的最长公共前后缀为0,所以不可能拼凑出比1大的)
因此,此时比较p[j]与p[k]的值(p[k]是第0位):
- 若相等,则 j++(要赋值给j+1),k++(让k=1),next[j] = k(j+1位的next值为1)
- 若不相等,则k=next[k](让k=-1),进入下次循环,j++,k++,将j+1位的next值赋为0
(2) 若j-1位的next值不为0,为k(因为此时next[j] == k),则比较j指向的那一位与k指向的那一位是否相同:
-
若相等,则 j++(要赋值给j+1),k++(让k=k+1),next[j] = k(j+1位的next值为k+1)
-
若不等,则k回溯,k = next[k]
难点!!!!!!!!!
为什么k要等于next[k]???
因为此时,k位置与j位置失配,已经不可能找到最长的最长公共前后缀了,但是仍然有希望去找到较小的最长公共前后缀。因此,我们要回去找除了k-1与j-1相配外,还有哪一位与j-1相配,这不正是next[j]所记录的嘛?
KMP算法实现
有了next数组后,KMP算法就相当简单
public static int KMP(String ts, String ps) {
char[] t = ts.toCharArray();
char[] p = ps.toCharArray();
int i = 0; // 主串的位置
int j = 0; // 模式串的位置
int[] next = getNext(ps);
while (i < t.length && j < p.length) {
if (j == -1 || t[i] == p[j]) {
// 当j为-1时,要移动的是i,当然j也要归0
i++;
j++;
} else {
// i不需要回溯了
// i = i - j + 1;
j = next[j]; // j回到指定位置
}
}
if (j == p.length) {
return i - j;
} else {
return -1;
}
}
参考的一些博客:
https://www.cnblogs.com/yjiyjige/p/3263858.html
https://blog.csdn.net/qq_37969433/article/details/82947411
https://www.zhihu.com/question/21923021