KMP详细讲解
部分匹配值
以字符串"ABCDABC"为例
前缀:A,AB,ABC,ABCD,ABCDA,ABCDAB
后缀:BCDABC,CDABC,DABC,ABC,BC,C
“A"的前缀和后缀都为空集.共有元素的长度位0
“AB"的前缀为"A”,后缀为"B”,共有元素的长度为0
“ABC"的前缀为"A”,“AB”,后缀"BC",“C”,共有元素的长度为0
“ABCD"的前缀为"A”,“AB”,“ABC”,后缀"BCD",“CD”,“D”,共有长度为0
“ABCDA"的前缀为"A”,“AB”,“ABC”,“ABCD”,后缀"BCDA",“CDA”,“DA”,“A”,共有元素为"A",长度为1
“ABCDAB"的前缀为"A”,“AB”,“ABC”,“ABCD”,“ABCDA”,后缀"BCDAB",“CDAB”,“DAB”,“AB”,“B”,共有元素为"AB",长度为2
因此对于字符串ABCDABC它的部分匹配表为:
A B C D A B D
0 0 0 0 1 2 0
KMP的next数组
KMP的next数组讲解
KMP完整代码
package algorithm.DataStruct.Frequentlyused.KMP;
/**
* @author: Serendipity
* Date: 2022/2/15 21:24
* Description:
* KMP方法算法就利用之前判断过信息,通过一个nexit数组,保在模式串中前后最长公共子序列的长度,
* 每次回溯时,通过next数组找到,前面匹配过的位置,省去了大量的计算时间
*/
public class KMP {
public static void main(String[] args) {
String str = "尚硅谷你三个功能爱上啊实打实带宽理解";
String str1 = "打实带";
System.out.println(getIndexOf(str,str1));
}
public static int getIndexOf(String str,String substr){
int[] next = kmpNext(substr);
return getIndexOf(str,substr,next);
}
public static int getIndexOf(String str,String substr,int[]next){
for(int i=0,j=0;i<str.length();i++){
while(j>0 && str.charAt(i)!=substr.charAt(j)){
j=next[j-1];
}
if(str.charAt(i)==substr.charAt(j)){
j++;
}
if(j==substr.length()){
return i-j+1; //这里要+1是因为退出的时候i还没有++
}
}
return -1;
}
/**
* 该函数用于获取字符串的部分匹配表next
* @param substr 待获取部分匹配表的字符串
* @return 返回该字符串的部分匹配表
* 理解:next[0]直接设置为0即可
* next[k]=x代表的是从当前这个字符开始有x个字符与从头开始的x字符是一样的
* abcabc->0 0 0 1 2 3 k=3时x=1 代表str.charAt(3)(当前字符)==str.charAt(0)(从头开始的x个字符);
* KMP算法可以理解为看门牌算法 因为当next[k]>0的时候 可以得到
* str.charAt(k)(当前字符)==str.charAt(next[k]-1)(从头开始的x个字符);
* k=4时x=2 那么代表str.charAt(4)(当前字符'b')==str.charAt(next[4]-1)('b')(从头开始的x个字符);
* str.charAt(3)(当前字符'a')==str.charAt(next[3]-1)('a')(从头开始的x个字符);
* 因此再我们要求next[k+1](比如next[5])的时候,由于我们肯定知道next[k](next[4]=2),因此代表str.charAt(k+1)如果
* 等于str.charAt(next[k]=2)(注:此处k=4),那么就说明next[k+1]=3,如果不等,例如为a b c a b d,也就是
* str.charAt(k+1)!=str.charAt(next[k]=2),那么此时k=next[next[k]=2]=0;此时判断str.charAt(0)==str.charAt(5)
* 不相等因此令next[5]=0;
*/
public static int[]kmpNext(String substr){
int[]next = new int[substr.length()];
next[0]=0;
//i是字符串后缀 j是字符串前缀,同时也是最长字串前后缀相等的长度
for(int i=1,j=0;i<substr.length();i++){
//不相等的时候就一直找知道找到相等的
while(j>0&&substr.charAt(i)!=substr.charAt(j)){
j=next[j-1];
}//相等就++
if(substr.charAt(i)==substr.charAt(j)){
j++;
}
next[i]=j;//然后赋值
}
return next;
}
/**
* kmp的next数组的第二种得到方法
* next[k]的值代表的是前面的next[k]个字符与后面的next[k]个字符
* 这里的前面和后面指的是next[k]他对应的字符对应的前面的字符和后面的字符
* 例如next[k]=3,那么说明这个next[k]对应的前面三个字符和从头开始的三个字符一定相等
* 与上面那种只有一点点差异
* @param str
* @return
*/
public static int[]getKMPNext(String str){
int[]next=new int[str.length()];
next[0]=str.length();
next[1]=0;
int i=1,j=0;
while(i<next[0]){
if(j==0||str.charAt(i)==str.charAt(j)){
next[++i]=++j;
}else{
j=next[j];
}
}
return next;
}
public static int kmp(String str,String substr){
return -1;
}
/**
* 暴力匹配法
*
* @param str 待匹配字符串
* @param substr 字串
* @return -1代表没有字串 否则返回字串第一个字符再字符串中的位置
*/
public static int violentMatch(String str, String substr) {
char[] chars = str.toCharArray();
char[] subchars = substr.toCharArray();
int i = 0, j = 0;
while (i < chars.length && j < subchars.length) {
if (chars[i] == subchars[j]) {
i++;
j++;
} else {
i = i - (j - 1);//i每次总是增加和j一样的数量
j = 0; //因此如果这次匹配失败那么i只需要下移1位即可
} //因此还需要减掉和j一样的前进步数
}
if (j == subchars.length) {
return i - j;//这次找到了 那么只需要将i-j的长度即可得到这个字符首次出现的位置
} else {
return -1;
}
}
}