在这先介绍字符串中前缀、后缀的概念,首先前缀是必包含第一个元素,不包含最后一个元素的;而后缀是必包含最后一个元素,不包含第一个元素的。一个字符串的前缀后缀是一个集合,可以有很多个。
例如,数组[b,a,b,a,b]中,前缀有{b},{b,a},{b,a,b},{b,a,b,a};后缀有{b},{a,b},{b,a,b},{a,b,a,b}
其中前缀和后缀的最长匹配度,即为它们最长能相同的字符数,即为{b,a,b},所以最长匹配度就是3.
如果我们有一个字符串S "babababcbabababb",有一个模板P ”bababb“,问S中是否有P存在,若存在则返回其在S中的位置。
这题我们固然可以用暴力法去破解,然而时间复杂度太高了,所以可以使用KMP算法求解。这里我们只是理解一下KMP算法,具体原理并不会讲。
首先我们要设一个指针i指向S的字符,之后会用i去扫描每个字符;设一个指针j指向P的字符,之后会用j去扫描每个字符。(先说一下,在KMP算法中i不需要回溯,只有j可能回溯)
KMP算法是如何来检测字符是否匹配的呢?
下面我们来绘图解释一下:
这个图是KMP匹配的过程,(标有红色的是S和P失配时的字符。注:此处P有多行只是将P每次失配过程按行展开而已)
- 首先i和j都从开头开始匹配,直到i==5,j==5时,我们看到P中第一行的那个红色b的那里,S[i]!=P[j],所以失配;(第一行时)
- 然后,i不变(记住不会像暴力法那样返回0),j是变成失配前的子数组{b,a,b,a,b}的前缀后缀最长匹配长度的值,即为3;(第二行时)
- 随后发现S[i]=c和P[j]=a,失配,i不变,j变成{b,a,b}的前缀后缀最长匹配长度的值,即为1;(第三行)
- 此时S[i] = c, P[j] = a,失配,i不变,j变成{b}的最长匹配长度的值,即为0;(第四行)
- 这时,S[i] = c, P[j] = b,还是失配,此时因为j已经为0了(最长匹配长度可以认为是-1了),即j<0,所以此时i++,j++。(第五行)
- 这时,一开始S[i] = b, P[j] = b,匹配,则i和j都加1,直到再次失配时,即为第六行红色的b那里P[j] = b时,再次失配,i继续不变,j继续回溯到失配前的子数组{b,a,b,a,b}的最长匹配长度的值那里,即j=3;(第六行)
- 最后当j等于P.length时,即为完全匹配成功,此时只需返回i-j的值,即为P在S中的位置
由上述过程我们可以看出,最关键的是把j回溯成前缀后缀最长匹配长度的值,所以构建的next数组即是存储这个值。
说了这么多,我们还是用代码来好好看看next是如何构建的呢?
public class KMP {
public static void main(String[] args) {
String s = "babababcbabababb";
String p = "bababb";
System.out.println(indexOf(s, p));
}
public static int indexOf(String s, String p) {
if(s.length() == 0 || p.length() == 0) return -1;
if(p.length() > s.length()) return -1;
int[] next = next(p);
int i = 0; //s位置
int j = 0; //p位置
int sLen = s.length();
int pLen = p.length();
while(i < sLen) {
//1.如果j = -1, 或者当前字符匹配成功(即S[i] == P[j]),都令i++,j++
//j = -1,因为next[0]=-1,说明p的第一位和i这个位置无法匹配,这时i,j都增加1
//i移位,j从0开始
if(j == -1 || s.charAt(i) == p.charAt(j)) {
i++;
j++;
} else {//失配
//2.如果j!=-1,且当前字符匹配失败(即S[i]!=P[j]),即令i不变,j=next[j]
//next[j]都为j所对应的next值
j = next[j]; //回溯
//上述 j = next[j]的意思是:j失配,j回溯到next[j],记住next[j]是一个值一个数!
}
if(j == pLen) { //匹配成功
/* 可用来统计匹配的次数
count++;
i--;
j = next[j-1];
*/
return (i-j);
}
}
return -1;
}
public static int[] next(String ps) {
int pLength = ps.length();
int[] next = new int[pLength]; //next数组
char[] p = ps.toCharArray(); //模式字符串
next[0] = -1; //显然可以直接把next[0]赋值成-1
if(ps.length() == 1)
return next;
next[1] = 0; //显然可以把next[1]赋值成0
int j = 1;
int k = next[j]; //看看位置j的最长匹配前缀在哪里
while(j < pLength - 1) {
//现在要推出next[j+1],检查j和k位置上的关系即可
if(k < 0 || p[j] == p[k]) {
next[++j] = ++k;
} else { //k>=0 && p[j]!=p[k]
k = next[k];
}
}
return next;
}
}
em...其实这里我也不太知道next值具体是怎样计算的...原理是啥什么的也一脸懵...以后知道了再来修改博客...