十九、字符串匹配算法
1、暴力匹配
用子字符串去依次匹配主字符串的每一个字符,直到匹配完子字符串的全部字符(找到了)或匹配完主字符串的全部字符(没找到)。
需要两个索引,分别记录匹配主字符串和匹配子字符串的字符索引位置,如果遇到相同的字符,则后移两个索引位置,否则就将主字符串匹配索引移动到当前索引位置减去子字符串索引位置,再后移一位,并重置子字符串索引位置为0,然后再重新匹配主字符串的字符元素。
示例
public int violenceSearch(final String str, final String subStr) {
if (null == str || null == subStr) {
return -1;
}
int strLen = str.length();
int subStrLen = subStr.length();
if (strLen < subStrLen) {
return -1;
}
int i = 0;
int j = 0;
while (i < strLen && j < subStrLen) {
if (str.charAt(i) == subStr.charAt(j)) {
i++;
j++;
} else {
i = i - j + 1;
j = 0;
}
if (j == subStrLen) {
return i - j;
}
}
return -1;
}
2、KMP算法
Knuth-Morris-Pratt字符串查找算法,由Donald Ervin Knuth
、James Hiram Morris
和Vaughan Pratt
三人于1977年联合发表。
核心是当匹配失败后,利用子字符串的局部匹配信息,尽量减少子字符串和主字符串的匹配次数,避免重新检查先前匹配过的字符,以达到快速匹配的目的。
子字符串的部分匹配表:需要两个匹配索引,用于判断字符串的以头字符开头的顺序子串,是否重复出现过。如果出现过,则延续匹配索引值,否则就回溯到上次的匹配索引值,直到找到当前字符出现过的索引值或值为0(第一个索引值)。
String str = "AABC"; // 字符串的开头是'A',后面又出现了一次
[0, 1, 0, 0]
String str = "12312312"; // 字符串的开头是'1'、'2'、'3',后面又按照这个顺序,出现了五次
[0, 0, 0, 1, 2, 3, 4, 5]
String str = "caac"; // 字符串的开头是'c'、'a',后面又出现了一次
[0, 0, 0, 1]
示例
public int kmpSearch(final String str, final String subStr) {
if (null == str || null == subStr) {
return -1;
}
int strLen = str.length();
int subStrLen = subStr.length();
if (strLen < subStrLen) {
return -1;
}
int[] next = next(subStr);
for (int i = 0, j = 0; i < strLen; i++) {
// 没有匹配上,回溯到,上次匹配到的位置 + 部分匹配子串的长度
while (0 < j && str.charAt(i) != subStr.charAt(j)) {
j = next[j - 1];
}
if (str.charAt(i) == subStr.charAt(j)) {
j++;
}
if (j == subStrLen) {
return i - j + 1;
}
}
return -1;
}
/**
* 获取子字符串的部分匹配表
*/
private int[] next(final String subStr) {
int len = subStr.length();
int[] next = new int[len];
for (int i = 1, j = 0; i < len; i++) {
/*
* 如果没有匹配上,则回溯匹配索引值,
* 直到匹配上为止或重置匹配索引值为0【部分匹配表的第一个元素一直是0】
* next[0] = 0;
*/
while (0 < j && subStr.charAt(i) != subStr.charAt(j)) {
j = next[j - 1];
}
// 如果重复出现了子字符,则延续匹配索引值
if (subStr.charAt(i) == subStr.charAt(j)) {
j++;
}
next[i] = j;
}
return next;
}