1.思路
字符串匹配,通常我们使用的是互相比较,也就是 str1 : ABCABCD st2 : ABCABCT
str1 和 str2按照顺序比较。当匹配到str1的D 和 str2的T位置可以发现,不等。则直接从str1的第一个A+1位置在匹配。可以发现这样就是穷举的方法,相对来说是一种时间复杂度比较高的。因此,就出现了KMP算法。
KMP字符串匹配算法: KMP的核心思想是先计算出当前字符的最长前缀和最长后缀的最长长度。不包含第一个前缀和最后一个后缀。
举一个栗子,ABCABCD D的最长子串是多少 为ABC ABC 为3,而AAAAD D的最长子串长度为3 不能包含第一个A和最后一个A。而在计算KMP算法之前需要先计算出最长子串的长度。ababac 的最长子串的长度分别为 [-1,0,0,1,2,3]。
基于最长子串长度这个数组,就可以实现KMP算法。还是计算str1: abkababkabd str2:abkababkabf 当str1移动到d位置 str2移动到f位置,发现不相等。而我们之前的做法是将str1的指针移动到开头a+1的位置,也就是b的位置从先计算。但是KMP算法基于之前计算的最长子串数组,可以有一个很大的改进,那就是不需要从a+1的位置开始。
ps:重点来了。会取出str2中f的前一个位置最长子串数组的长度。为5(最长串为 abkab)从str1的a+5位置比较。这样就避免了a+1 一个位置一个位置的重复比较。为什么这样比较呢。因为在str1中的第一个最长子串abkab已经比较过了。不存在相同的,所以直接从a+5的位置比较。
那么在str1 a到a+5位置开始的位置到最终str1末尾的位置会不会存在一个和str2想匹配的字符串呢。
我们假设存在一个上述的条件,在已知str2最长子串中,如果存在一个那么势必就是str1的最长后缀子串,所以第一个条件是不存在的。
好了,上面就是kmp算法的核心。
接下来,我们来梳理一下KMP算法的流程。
getNextArray();首先需要初始化0 1 位置的参数,pos的表示当前位置大小。而cn表示当前位置的之前的最长子串的长度为多少。如果当前位置的前一个位置str2[pos-1]和str2[cn] 也就是最大子串的长度的位置元素相等。当前pos的cn++.
2.code
package com.ncst.improve.one;
/**
* @author i
* @create 2020/6/30 17:04
* @Description
*/
public class KMP {
public static int kmp(String str1,String str2){
if (str1 == null || str2 == null || str1.length()<str2.length() || str2.length() < 1){
return -1;
}
char [] ch1 = str1.toCharArray();
char [] ch2 = str2.toCharArray();
int c1 = 0;
int c2 = 0;
int [] next = getNextArray(ch2);
while (c1<ch1.length && c2<ch2.length){
if (ch1[c1] == ch2[c2]){
c1++;
c2++;
}else if (next[c2] == -1){
c1++;
}else {
c2 = next[c2];
}
}
return c2 == ch2.length ? c1-c2 : -1;
}
public static int [] getNextArray(char [] str2){
if (str2.length == 1){
return new int [-1];
}
int [] next = new int [str2.length];
next[0] = -1;
next[1] = 0;
int pos = 2;//走到那个位置
int cn = 0;//当前位置的最长子串个数
while (pos<next.length){
if (str2[pos-1] == str2[cn]){
next[pos++] = ++cn;
}else if (cn>0){
cn = next[cn];
}else {
next[pos++] = 0;
}
}
return next;
}
public static void main(String[] args) {
String str = "abdabc";
String str2 = "abc";
System.out.println(kmp(str, str2));
}
}