目录
1. 字符串匹配场景
有一个字符串sourceStr = “BBC ABCDAB ABCDABCDABDE”,和一个待匹配子字符串matchsStr = “ABCDABD”。如果sourceStr含有matchsStr, 则返回第一次匹配的第一个index, 如果不含则返回-1
2. 暴力匹配算法
2.1 暴力匹配算法介绍
假设现在sourceStr匹配到i位置,待匹配子字符串matchsStr匹配到j位置,则有:
- 如果当前字符匹配成功(即sourceStr[i] == matchsStr[j]),则i++,j++,继续匹配下一个字符
- 如果不匹配(即sourceStr[i] != matchsStr[j]),令i = i - j + 1,j = 0。相当于每次匹配失败时,i回溯到下一位继续匹配,j被置为0
2.2 暴力匹配算法程序实现
public class ViolenceMatch {
public static void main(String[] args) {
String sourceStr = "BBC ABCDAB ABCDABCDABDE";
String matchStr = "ABCDABD";
int matchIndex = violenceMatch(sourceStr, matchStr);
System.out.println("matchIndex = " + matchIndex);
}
// 暴力匹配算法实现
public static int violenceMatch(String sourceStr, String matchStr) {
char[] sourceChars = sourceStr.toCharArray();
char[] matchChars = matchStr.toCharArray();
int sourceLength = sourceChars.length;
int matchLength = matchChars.length;
// i索引指向sourceChars
int i = 0;
// j索引指向matchChars
int j = 0;
while (i < sourceLength && j < matchLength) {
// 如果匹配成功
if (sourceChars[i] == matchChars[j]) {
i++;
j++;
} else {
// 如果没有匹配成功
i = i - (j - 1);
j = 0;
}
}
// 当matchStr完全匹配成功之后,进行了j++,则j == matchLength
if (j == matchLength) {
// 返回第一次匹配的第一个index
return i - j;
} else {
return -1;
}
}
}
运行程序,结果如下:
matchIndex = 15
3. KMP匹配算法
3.1 KMP匹配算法介绍
KMP算法的目的是减少不必要的匹配,提高匹配效率。KMP算法就是利用matchStr形成一个部分匹配表,通过一个partMatch数组,保存matchStr中,截止当前字符形成的字符串,的前后缀最大公共字符串的长度
每次回溯时,通过partMatch数组,不断的迭代,以便重新找到matchStr的前缀,和sourceStr中截止当前字符形成的字符串的后缀的最大公共字符串,然后继续进行匹配
3.2 KMP匹配算法实现的基础知识
3.2.1 前缀和后缀的概念
对于字符串"bread"
前缀有:b、br、bre、brea
后缀有:read、ead、ad、d
3.2.2 前缀和后缀的最大公共字符串长度
以"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
3.2.3 部分匹配表
部分匹配表就是,matchStr截止当前字符形成的字符串,的前缀和后缀的最大公共字符串的长度。如下所示:
字符串 | A | B | C | D | A | B | D |
---|---|---|---|---|---|---|---|
部分匹配值 | 0 | 0 | 0 | 0 | 1 | 2 | 0 |
3.3 KMP匹配算法程序实现
import java.util.Arrays;
public class KMPMatch {
public static void main(String[] args) {
String sourceStr = "BBC ABCDAB ABCDABCDABDE";
String matchStr = "ABCDABD";
int[] partMatchArray = getPartMatchArray(matchStr);
System.out.println("partMatchArray = " + Arrays.toString(partMatchArray));
int index = kMPMatch(sourceStr, matchStr, partMatchArray);
System.out.println("index = " + index);
}
// 获取matchStr部分匹配值表
public static int[] getPartMatchArray(String matchStr) {
// 保存部分匹配表的数据
int[] partMatchArray = new int[matchStr.length()];
// 第一个字符的前后缀都为空,最大公共字符串长度为0
partMatchArray[0] = 0;
// 从第二个字符开始进行遍历处理。并定义一个指针prefixPointer指向matchStr的第一个字符, 用于前缀匹配
for (int suffixPoint = 1, prefixPoint = 0; suffixPoint < matchStr.length(); suffixPoint++) {
// 对于一个新处理的字符,如果匹配不成功,则需要找出后缀和后缀新的公共字符串,再在新的公共字符串上进行匹配
while (prefixPoint > 0 && matchStr.charAt(prefixPoint) != matchStr.charAt(suffixPoint)) {
// 这里举例说明:
// 例如"ABACABAD", 当前处理字符为"D", prefixPoint指向字符"C"
// "ABA"为处理上一个字符"A"的最大公共字符串
// 从"ABA"中找到新的公共字符串"A",即找到的"A"是下一次前缀匹配的前一部分,找到的"A"也是下一次后缀匹配的前一部分
prefixPoint = partMatchArray[prefixPoint - 1];
}
// 在公共字符串(前缀的前一部分,后缀的前一部分)的基础上,对于一个新处理的字符,如果匹配成功,则公共字符串长度加1
if (matchStr.charAt(prefixPoint) == matchStr.charAt(suffixPoint)) {
// 前缀的指针进行后移(公共字符串变长),后缀的指针在for循环中进行后移
prefixPoint++;
}
// 保存当前处理字符的公共字符串长度
partMatchArray[suffixPoint] = prefixPoint;
}
return partMatchArray;
}
// KMP匹配算法实现
public static int kMPMatch(String sourceStr, String matchStr, int[] partMatchArray) {
// 遍历sourceStr进行处理。并定义一个指针matchPointer指向matchStr的第一个字符
for (int sourcePointer = 0, matchPointer = 0; sourcePointer < sourceStr.length(); sourcePointer++) {
// 对于一个新处理的字符,如果匹配不成功,则需要将matchPointer进行前移
while (matchPointer > 0 && sourceStr.charAt(sourcePointer) != matchStr.charAt(matchPointer)) {
// 这里举例说明:
// 例如sourceStr = "ABACABAD", sourcePointer指向字符"C"
// matchStr = "ABAD", matchPointer指向字符"D"
// "ABA"为已经匹配过的公共字符串
// 从"ABA"中找到新的公共字符串"A",即sourceStr的"ABA"的最后一个”A“,和matchStr的”ABA“的第一个”A“重叠
// sourceStr的”ABA“的最后一个"A”不用重新匹配,matchStr的matchPointer直接指向"ABA"的“B”,让sourceStr的“C”和matchStr的“B"进行匹配即可
matchPointer = partMatchArray[matchPointer - 1];
}
// 对于一个新处理的字符,如果匹配成功,则matchPointer后移,sourcePointer在for循环中进行后移
if (sourceStr.charAt(sourcePointer) == matchStr.charAt(matchPointer)) {
matchPointer++;
}
// 表示matchStr所有字符全部匹配到了,找到了
if (matchPointer == matchStr.length()) {
// 返回第一次匹配的第一个index
return sourcePointer - (matchPointer - 1);
}
}
// 没有匹配到, 返回-1
return -1;
}
}
运行程序,结果如下:
partMatchArray = [0, 0, 0, 0, 1, 2, 0]
index = 15