字串的定位操作通常称做串的模式匹配
BF算法
朴素的模式匹配算法。
对主串的每一个字符作为字串开头,与要匹配的字符串进行匹配。对主串做大循环,每个字符开头做T的长度的小循环,直到匹配成功或全部遍历完成为止。
输出结果:
从第5开始匹配
主串s:goodgooglehhhh
字串t: google
public class BFDemo {
// charAt取值从0开始
static int index(String s,String t,int pos) {
int i = pos; //主串下标
int j = 0; //子串下标
// 当i小于s长度,并且j小于t长度时循环
while (i < s.length() && j < t.length()) {
if (s.charAt(i) == t.charAt(j)) { // 两字母相等则继续
++i;
++j;
} else { // 后退并重新开始匹配
i = i -j + 1; // i退回到上次匹配首位的下一位
j = 0; // j退回到字串t的首位
}
}
if (j+1 >= t.length())
return i - t.length()+1;
else
return 0;
}
public static void main(String[] args) {
String s = "goodgooglehhhh";
String t = "google";
System.out.println("从第"+index(s, t, 0)+"开始匹配");
System.out.println("主串s:"+s);
System.out.print("字串t:");
for (int i = 0; i < index(s,t,0)-1; i++) {
System.out.print(" ");
}
System.out.println(t);
}
}
KMP算法
由于朴素模式匹配算法的低效,D.E.Knuth、J.H.Morris和V.R.Pratt三位前辈发表一个模式匹配算法,可以大大避免重复遍历的情况,我们把它称为克努特-莫里斯-普拉特算法,简称KMP算法。
核心:避免不必要的回溯。问题由模式串决定,不是由目标串决定。
我们把T串各个位置的j值的变化定义为一个数组next,那么next的长度就是T串的长度。于是可以得到下面的函数定义:
输出结果:
主串s=abmababsasb
子串t=ababs
next数组=[-1,0,0,1,2,]
从第4元素开始,主串和子串匹配
abmababsasb
ababs
public class KMPTest {
public static int[] getNext(String t,int[] next) {
next[0] = -1;
// 1)
int j = 1,k = -1;
while (j < t.length()) {
if (k == -1 || t.charAt(j-1) == t.charAt(k)) {
k++;
next[j] = k;
} else {
next[j] = 0;
k = 0;
}
j++;
}
// 2)
// for (int j=1,k=-1; j < t.length(); j++ ) {
// if (k == -1 || t.charAt(j-1) == t.charAt(k)) {
// k++;
// next[j] = k;
// } else {
// next[j] = 0;
// k = 0;
// }
// }
return next;
}
public static int indexKMP(String s,String t,int[] next) {
int i=1; // 主串
int j=1; // 子串
while (i <= s.length() && j <= t.length()) { // 下标不超过串长
// j==0,子串已回溯到头;主串与子串匹配,两个串都继续匹配下一个字符
if (j == 0 || s.charAt(i - 1) == t.charAt(j - 1)) {
i++;
j++;
} else { // 主/子串不匹配,子串回溯,根据next
j = next[j-1]+1;
}
}
if (j > t.length()-1) //
return i - t.length();
else
return 0;
}
public static void main(String[] args) {
String s = "abmababsasb";
String t = "ababs";
int[] next = new int[t.length()];
next = getNext(t, next);
System.out.println("主串s="+s);
System.out.println("子串t="+t);
System.out.print("next数组=[");
for (int n : next) {
System.out.print("" + n + ",");
}
System.out.println("]");
System.out.println("从第"+indexKMP(s, t, next)+"元素开始,主串和子串匹配");
System.out.println(s);
for (int i = 0; i < (indexKMP(s,t,next)-1); i++) {
System.out.print(" ");
}
System.out.println(t);
}
}
改进版的KMP算法
类似"aaaaax"这样的模式串,如果使用上面的KMP算法,得到next数组是[0,1,2,3,4,5],则需要挨个回退比较。但是其实没有必要,因为前面5个都是a。
即,在next数组已得到的前提下,比较t[j]是否等于t[next[j]],如果相等,nextval[j]=next[next[j]];否则,nextval[j]=next[j]。
输出结果:
nextval===0 0 0 0 0 0 5
// 改进版KMP算法
public class KMPPlusDemo {
public static int[] get_nextval(String t,int[] nextval) {
int i = 1,j = 0;
nextval[1] = 0;
while (i < t.length()) {
if (j == 0 || t.charAt(i-1) == t.charAt(j-1)) {
++i;
++j;
if (t.charAt(i-1) != t.charAt(j-1)) {
nextval[i-1] = j-1;
} else {
nextval[i-1] = nextval[j-1];
}
} else {
j = nextval[j-1]+1;
}
}
return nextval;
}
public static void main(String[] args) {
String t = "aaaaaax";
int[] nv = new int[t.length()];
nv = get_nextval(t,nv);
System.out.print("nextval===");
for (int n : nv) {
System.out.print(n + " ");
}
}
}