1.素朴匹配算法介绍
一个字符串(模式串)在另一个字符串(主串)中的位置,称为字符串模式匹配。
在朴素的字符串模式匹配算法中,我们对主串S和模式串T分别设置指针i和j,假设字符串下标从0开始,初始时i和j分别指向每个串的第0个位置。在第n趟匹配开始时,i指向主串S中的第n-1个位置,j指向模式串T的第0个位置,然后逐个向后比较。若T中的每一个字符都与S中的字符相等,则称匹配成功,否则,当遇到某个字符不相等时,i重新指向S的第n个位置,j重新指向T的第0个位置,继续进行第n+1趟匹配。
2.素朴匹配算法规则介绍:
可以看出,用朴素算法进行匹配时,第二、三、四、五次匹配均为没有必要的,因为子串自身无重复,且子串与主串的 0-4 位相等,所以子串的第 0 位必定与主串的第 1、2、3、4位不等。
3.KMP算法介绍
在进行字符串匹配时,KMP算法与朴素算法最大的区别就在于KMP算法省去了主串与子串不必要的回溯,这也是KMP算法(在主串有较多重复时)更加高效的关键。
从上述例子可以看出KMP算法的第一个优点:避免了主串不必要的回溯。事实上,主串的任何回溯都是不必要的,所以在KMP算法中,任何情况下主串都不回溯。
4.KMP算法应用:
我们不可能人工推算这些数值,因此我们需要一个数组来记录子串应回溯到的位置。
已知空格与 D 不匹配时,前面六个字符”ABCDAB”是匹配的。查表可知,最后一个匹配字符 B 对应的”部分匹配值”为 2,因此按照下面的公式算出向后移动的位数:
移动位数 = 已匹配的字符数 - 对应的部分匹配值
因为 6 - 2 等于 4,所以将搜索词向后移动 4 位。
部分匹配表:
“部分匹配值”就是”前缀”和”后缀”的最长的共有元素的长度。以”ABCDABD”为例,
-”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;
-”ABCDABD”的前缀为[A,AB, ABC, ABCD, ABCDA, ABCDAB],后缀为[BCDABD, CDABD, DABD, ABD, BD,
D],共有元素的长度为 0。
5.代码实现:
package algorithm;
/**
* @author WuChenGuang
*/
public class KMP {
public static void main(String[] args) {
System.out.println(KMP("ABCDE","ABC",0));
}
public static int KMP(String s,String t,int pos){
int i = pos-1;
int j = -1;
int[] next = getNext(t);
while (i<s.length() && j<t.length()){
if (j==-1 || s.charAt(i) == t.charAt(j)){
j++;
i++;
}else {
j = next[j];
}
}
if (j == t.length()){
return i-t.length();
}else {
return -1;
}
}
public static int[] getNext(String t){
int[] next = new int[t.length()];
int m = 0;
int n = -1;
next[0] = -1;
while (m<t.length()-1){
if (n==-1 || t.charAt(m) == t.charAt(n)){
m++;
n++;
if (t.charAt(m) !=t.charAt(n)){
next[m] = n;
}else {
next[m] = next[n];
}
}else {
n = next[n];
}
}
return next;
}
}
运行结果: