KMP算法:KMP算法是由Knuth,Morris,Pratt三位学者研究出的模式匹配算法,大大的避免了重复遍历的情况。
算法目的:确定主串中所含子串第一次出现的位置。
在这之前呢,有一个BF算法 (~暴力匹配~)。>_<
BF算法设计思想:
1,将主串的第pos个字符和模式的第1个字符比较,
2,若相等,继续逐个比较后续字符;
3,若不等,从主串的下一字符(pos+1)起,重新与第一个字符比较。
4,直到主串的一个连续子串字符序列与模式相等 。返回值为S中与T匹配的子序列第一个字符的序号,即匹配成功。
5,否则,匹配失败 。
public static int BF(String str,String sub,int pos){
//1.判断pos合法性。
if(pos < 0 || pos > str.length()){
return -1;
}
//2.开始查找。
int i = pos;
int j = 0;
//3.遍历主串和子串。
while(i = sub.length()){
return i-j;
}else{
return -1;
}
}
(回溯就是坚持条条大路通罗马的决心,然后遇到挫折就回到跌倒的地方重新爬起来,继续往前,这种思想是好的,但效率非常低)
——————————————————————————————————
—_— 所以啦,这个效率这么低。就肯定会有大神开始苦心研究。。。。终于有了现在的KMP算法啦^-^
KMP算法的核心也就是避免不必要的回溯
接下来,我们来认真分析一下KMP算法啦
1>
由上图可知两个字符串是在第五个失配
i1=j1
i2=j2 j1 !=j2 所以----->j1 != i2
i3=j3 j1 !=j3 所以----->j1 !=i3
...
因此失配后j回溯到j1,但是i不需要回溯
2>
由上图可知两个字符串是在第三个失配
i1=j1
i2=j2 j1=j2 所以----->j1=i2
因此失配后j回溯到j2,但是i不需要回溯
3>
由上图可知两个字符串是在第六个失配
i1=j1
i2=j2
i4=j4 j1=j4 ----->j1=i4
i5=j5 j2=j5------>j2=i5
因此失配后j回溯到j3,但是i不需要回溯
4>
i4=j1
i5=j2 j1=j2 所以------->i5=j1
i6=j3 j2=j3 所以-------->i6=j2
i7=j4 j3=j4 所以------->i7=j3
因此失配后j回溯到j4,但是i不需要回溯
next[]数组:当模式匹配串T失配的时候,next[]数组对应的元素应该用串T的哪个元素进行下一轮的匹配。
由前面的规律总结出next[]数组
每个元素的next数组为(模式串中前缀和后缀相同的个数加一)(ps:下标从0开始的next数组就是前缀和后缀相同的个数)
子串T T0......Tk-1 = Tj-k.......Tj-1
怎样得到next数组
首先假设:next[i] = k 成立,就有 p0.....pk-1 = px.....pi-1 得到 p0....pk-1 = pi-k...pi-1 --------------->再假设如果pk = pi;我们可以得到p0.....pk = pi-k....pi;那这个就是next[i+1] = k+1;
假设 pk!= pi
就继续回溯
k = next[k];
public static void getNext(String sub,int[] next){
next[0] = -1;
next[1] = 0;
int k = 0;
int i = 2;
if(k == -1 || sub.charAt(k) == sub.charAt(i-1)){
next[i] = k+1;
k++;
i++;
}else{
k = next[k];
}
}
public static int kmp(String str,String sub){
int i = 0;
int j = 0;
int[] next = new int[sub.length()];
getNext(sub,next);
while(j < sub.length()){
if(j == -1 || sub.charAt(j) == str.charAt(i)){
i++;
j++;
}else{
j = next[j];
}
}
if(j >= sub.length()){
return i-j;
}else{
return -1;
}
}
后来呢,就有人发现KMP算法有缺陷
(因为i5和j5失配,i1,i2,i3,i4都等于i5,所以我们一眼就能看出i1,i2,i3,i4,也都是失配的。)
所以我们只需要在next[]数组的计算中加上判断,如果它的下一个元素与前缀元素相等,那么它应该回溯到前缀元素的next[]数组值那儿去。
public static void getNextval(String sub,int[] next){
next[0] = -1;
next[1] = 0;
int k = 0;
int i = 2;
if(k == -1 || sub.charAt(k) == sub.charAt(i-1)){
if(sub.charAt(k) == sub.charAt(i-1)){
next[i] = next[k];
}else{
next[i] = k+1;
}
k++;
i++;
}else{
k = next[k];
}
}