package algorithm;
/*
* KMP算法
* 字符串和模式串的匹配问题
设主串(下文中我们称作S)为:BBC ABCDAB ABCDABCDABDE
模式串(下文中我们称作P)为:ABCDABD
* 1、暴力算法匹配
* 如果s[i]!=p[j],则回溯 p[0],s则为i=i-j+1,移动一位。
*
* 2、KMP算法
i为遍历S,j为遍历P
1、首先s[0],s[1],s[2],s[3]与p[0]均不匹配。则直接为:
S:BBC ABCDAB ABCDABCDABDE
P: ABCDABD
2、S[4]=A 与 P[0]匹配,一直到S[10]与P[6]不匹配。暴力匹配为i=5,则j=0,重新匹配。此时S[5]=B 与P[0]=A 不匹配,
S:BBC ABCDAB ABCDABCDABDE
P: ABCDABD
以前S[5]=P[1],而P[1]!=P[0],故一定不匹配。利用以前匹配过的来匹配当前的。
3 、保持i不变,如何让j改变,去动态适应呢。
KMP算法的定义:
(1)如果字符串匹配,即S[i]==P[j],则i++,j++,继续匹配下一个字符
(2)如果字符串匹配不成功,即S[i]!=P[j],则i改变,j=next[j],此时j向右移动了j-next[j]位置。
S:BBC ABCDAB ABCDABCDABDE
P: ABCDABD
next[6]=2,即i=10不变,j=next[6]=P[2],即S[10]与P[2]匹配
next[6]=2,此为j=6,此为D,前面为ABCDAB,此时ABCDAB中长度最大且相等的前缀和后缀,此为AB,只有此时模式串向右移动时,
正好与S匹配,因前面匹配,即S[9]=P[5]=p[1]=B,S[8]=P[4]=P[0]=A,
S:BBC ABCDAB ABCDABCDABDE
P: ABCDABD
发现S[10]!=P[2],则next[2],此时C前为AB,则next[2]=0
S:BBC ABCDAB ABCDABCDABDE
P: ABCDABD
此时由于S[10]!=P[0],则应该S[11]与P[0]比较
S:BBC ABCDAB ABCDABCDABDE
P: ABCDABD
发现S[17]=C与P[6]=D 不匹配,此时i=17,next[6]=2,j=6-2向右移动4位,则如下, 此后一直匹配,则成功。
S:BBC ABCDAB ABCDABCDABDE
P: ABCDABD
此时算法为 getKMPBeginPosition1;
(*i=0 j=0时,如果不匹配,则 next[j]=next[0],此时初始化next[0]=-1,此时i和j均需向前移动,即i++,和j++***)
*
*
* */
public class KMP {
public static int violenceMatch(String s, String p) {
if (s == null || p == null) {
return -1;
}
if (p.length() > s.length()) {
return -1;
}
int sLength = s.length();
int pLength = p.length();
int i = 0, j = 0;
while (i < sLength && j < pLength) {
char s1 = s.charAt(i);
char p1 = p.charAt(j);
if (s1 == p1) {
i++;
j++;
} else {
i = i - j + 1;
j = 0;
}
}
if (j == pLength) {
return i - j;
}
return -1;
}
/*
*
* */
public int getKMPBeginPosition1(String S, String P) {
if (S == null || P == null) {
return -1;
}
if (P.length() > S.length()) {
return -1;
}
int sLength = S.length();
int pLength = P.length();
int[] next = getNextValue(P);
int i = 0, j = 0;
while (i < sLength && j < pLength) {
if (j == -1||S.charAt(i) == P.charAt(j)) {
i++;
j++;
} else { // 如果不匹配,则i不动,移动j,使j=next[j]
j = next[j];
}
}
if (j == pLength) {
return i - j;
}
return -1;
}
/*
* 求next[j],当S[i]!=P[j]时,i保持不变,只移动j,next[j]为j前面 前缀后缀最长公共元素长度 如 S:BBC ABCDAB
* ABCDABCDABDE P: ABCDABD 当S[10]与P[6]不匹配时,需要找到P[6]前 ABCDAB的前缀后缀最长公共元素长度,此时为
* AB,此为2,此时next[6]=2 (0,1,2)
*
*
* 已知:next[j]=k,求next[k+1]的值,即 在前移一个字符时,此时需比较:p[k]与p[j]
* (1)如果p[k]==p[j],则next[j+1]=next[j]+1=k+1
* (2)如果p[k]!=p[j],此时需要比较p[next[k]]与p[j],即j不变,k=next[k], 如果p[j]==p[next[k]],
* 则next[j+1]=next[k]+1, 一直递归,直到相同。
*
* 求next[0-6], p[0]=A,p[1]=B,p[2]=C,p[3]=D,p[4]=A,p[5]=B,p[6]=D
* j=0,next[0]=-1 j=1,即next[1]=0; j+1=2 ,j=1,k=0, p[0]!=p[1],递归
* j=1,k=next[k]=-1 ,则直接结束 next[2]=next[0]+1=0
*
* j+1=3,j=2,k=next[2]=0, 此时p[2]!=p[0], 继续递归,k=next[0]=-1,
* 则结束为next[3]=next[0]+1=0
*
* j+1=4,j=3,k=next[3]=0,则p[3]!=p[0],继续递归,k=next[0]=-1,则next[4]=-1+1=0
* j+1=5,j=4,k=next[4]=0,则p[4]==p[0],无需递归,next[5]=next[4]+1=1
*
* j+1=6,j=5,k=next[5]=1,则p[5]==p[1],则next[6]=next[5]+1=2
* j+1=7,j=6,k=next[6]
* =2,则p[6]!=p[2],递归k=next[2]=0,则p[6]!=p[0],k=next[0]=-1,则next
* [7]=next[0]+1=0
*/
//易出错的地方为: P.charAt(k) == P.charAt(j)
public int[] getNextValue(String P) {
if (P == null) {
return null;
}
int plength = P.length();
int k = -1;
int[] next = new int[plength];
next[0] = -1;
int j = 0;
while (j < plength - 1) {
if (k == -1 || P.charAt(k) == P.charAt(j)) { //
k++;
j++;
next[j] = k;
} else {
k = next[k];
}
}
return next;
}
public void printP(String p, int[] pints) {
for (int i = 0; i < p.length(); i++) {
System.out.println(p.charAt(i) + "->" + pints[i]);
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
KMP kmp = new KMP();
String s = "BBC ABCDAB ABCDABCDABDE";
String p = "ABCDABD";
//int[] pints = kmp.getNextValue(p);
//kmp.printP(p, pints);
int result=violenceMatch(s,p);
int r=kmp.getKMPBeginPosition1(s,p);
System.out.println("暴力破解算法,序号为:"+result+" ,直接indexOf算法为:"+s.indexOf(p));
System.out.println("KMP算法为,序号为:"+r);
}
}