一、是什么?
注意,是KMP算法,不是MMP哈,我没有骂人。KMP算法是用来做字符串匹配的,除了KMP算法分,还有暴力匹配算法,也是用来做字符串匹配的。接下来先看看暴力匹配算法,你就知道为啥会出现KMP算法了。
二、暴力匹配算法:
1. 算法思路:
假如现有两个字符串:
String str1 = "BBC ABCDAB ABCDABCDABDE";
String str2 = "ABCDABD";
假设现在str1匹配到i位置,str2匹配到j位置,则有:
str1[i] == str2[j]
,则i++; j++;
,继续匹配下一个字符;str1[i] != str2[j]
,则令i = i - (j - 1); j = 0;
,就是每次匹配失败,i被回溯,j置为0。怎么理解这个过程呢?
j
来遍历str2。一开始i=0,j=0
,所以是不匹配,j
就不变,i
就一直后移,直到i=4
的时候;i=4
时,A和A匹配上了,此时i
和j
都后移,直到i=10, j=6
的时候,D和空格不匹配;i=i-j+1=5
,j=0
,即str2又从第一个字符A开始去跟str1中的第六个字符B匹配。通过上面的描述可以发现,暴力匹配效率并不高,发现不匹配之后,回到前面第一次匹配的地方,往后移动一位,再开始匹配。每次只移动一位,会有大量回溯。
2. 代码实现:
public class ViolenceMatch {
public static int match(String str1, String str2) {
char[] charArr1 = str1.toCharArray();
char[] charArr2 = str2.toCharArray();
int arr1Len = charArr1.length;
int arr2Len = charArr2.length;
int i = 0; // 遍历charArr1的索引
int j = 0; // 遍历charArr2的索引
while(i<arr1Len && j<arr2Len) {
if (charArr1[i] == charArr2[j]) { // 匹配成功
i++;
j++;
} else {
i = i - j + 1;
j = 0;
}
}
if (j == arr2Len) {
return i - j;
} else {
return -1;
}
}
public static void main(String[] args) {
String str1 = "BBC ABCDAB ABCDABCDABDE";
String str2 = "ABCDABD";
System.out.println(match(str1, str2));
}
}
三、KMP算法:
1. 介绍:
KMP算法,是一个判断字符串是否在另一个字符串中出现过的算法,如果出现过,返回最早出现的位置。和暴力匹配算法不同的是,KMP算法会用一个next数组来保存字符串中前后最长公共子序列的长度,每次回溯时,通过next找到前面匹配过的位置,这样就省了大量的时间。
2. 案例:
看了介绍也不知道在说什么,直接看案例吧。现有如下字符串:
String str1 = "BBC ABCDAB ABCDABCDABDE";
String str2 = "ABCDABD";
现在要判断str1中是否包含str2,如果包含,返回str2在str1中第一次出现的位置,如果没有则返回-1。
思路:
i
来遍历str1
,用j
来遍历str2
;i=j=0
的时候,i
指向的是B,j
指向的是A,不匹配;j
不动,i
后移,指向的是第二个B,与j
所指的A还是不匹配,i
继续后移;i
指向了str1中第一个空格后面的那个A,才与j
指向的字符匹配了;搜索词 | A | B | C | D | A | B | D |
---|---|---|---|---|---|---|---|
部分匹配值 | 0 | 0 | 0 | 0 | 1 | 2 | 0 |
移动位数 = 已匹配的字符数 - 对应的部分匹配值
6 - 2 = 4,所以搜索词向后移动四位,即i向后移动四位。
……
3. 部分匹配表怎么来的?
一个字符串:ABCDAB,它的前缀有A,AB,ABC,ABCD,ABCDA,后缀有B,AB,DAB,CDAB,BCDAB。部分匹配值就是前缀和后缀的最长的共有元素长度。这里前缀和后缀共有元素是AB,AB的长度是2,所以值就是2。上面那张部分匹配表的求值过程:
4. KMP算法使用步骤:
5. 代码实现:
public class KmpDemo {
public static void main(String[] args) {
String str1 = "BBC ABCDAB ABCDABCDABDE";
String str2 = "ABCDABD";
System.out.println(match(str1, str2));
}
/**
* kmp获取子串在原串中第一次出现的位置
* @param str1 原串
* @param str2 子串
* @return
*/
public static int match(String str1, String str2) {
// 拿到部分匹配值表
int[] table = partMatchTable(str2);
// 遍历str1
for(int i=0, j=0; i<str1.length(); i++) {
while (j > 0 && str1.charAt(i) != str2.charAt(j)) {
j = table[j-1];
}
if (str1.charAt(i) == str2.charAt(j)) {
j++;
} else {
}
if (j == str2.length()) {
return i - j + 1;
}
}
return -1;
}
/**
* 获取str的部分匹配表
* @param str
* @return
*/
private static int[] partMatchTable(String str) {
int[] table = new int[str.length()];
table[0] = 0;
for(int i=1, j=0; i<str.length(); i++) {
while(j > 0 && str.charAt(i) != str.charAt(j)) {
j = table[j-1];
}
if (str.charAt(i) == str.charAt(j)) {
j++;
}
table[i] = j;
}
return table;
}
}