最近 Android 做了一个全文关键字高亮的功能,直接用了
Java 现成的 API 解决了,在查阅资料的过程中得知还有几种匹配算法:BF、RK、KMP、BM、Sunday,有空就做了一些了解。这里记录一下防止忘记,阮一峰大神关于这些算法的博客写的很好。
BF
暴力检索,这种方法最容易想到,也是最容易实现的,从首字母开始挨个的将关键字和做比对。用下面的图片就能只管的说明(图片来自阮一峰大神的博客)

代码实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| package other.string.textmatch;
public class BFMatch {
public static void BFMatch(String originText, String keyword) { char originChar; for (int i = 0; i < originText.length(); i++) { for (int j = 0; j < keyword.length(); j++) { if (i + j >= originText.length()) break; originChar = originText.charAt(i + j);
if (originChar != keyword.charAt(j)) { break; }
if (j == keyword.length() - 1) { System.out.println("找到匹配字符串,起始:" + i + " 终止:" + (i + keyword.length() - 1)); } } } }
public static void main(String... args) { BFMatch("asdfj9iwhefpnehbnfhodhsvb", "j9"); } }
|
输出:
找到匹配字符串,起始:4 终止:5
RK
RK 算法是对 BF 算法的一个改进,看了我上面对 BF 的实现不难发现,每次匹配都需要比对每一个字符是否一致,是否有更加有效率的方法呢?有的,RK 对于 BF 的改进就在于尝试进行一次比较来判断两者是否相等。RK 算法首先计算子串的哈希值,然后在原字符串中取出同样长度的字符串计算哈希值,如果二者的哈希值不等那么他们一定不同。如果哈希值相同,由于哈希冲突的存在,也需要再次比对一下是否相同。一般情况下我们需要匹配的文本含有的关键字占全文的数量应该不是很高,所以这种高效率去除不同的情况效率是高于 BF 的。看一下实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| package other.string.textmatch;
public class RKMatch {
public static void RKMatch(String originText, String keyword) { int keyHash = keyword.hashCode(); int keyLength = keyword.length();
String subString; for (int i = 0; i < originText.length(); i++) { if (keyLength + i >= originText.length()) break;
subString = originText.substring(i, i + keyLength); if (subString.hashCode() == keyHash) { for (int j = 0; j < keyLength; j++) { if (subString.charAt(j) != keyword.charAt(j)) break;
if (j == keyLength - 1) { System.out.println("找到匹配字符串,起始:" + i + " 终止:" + (i + keyword.length() - 1)); } } } } }
public static void main(String... args) { RKMatch("asdfj9iwhefpnehbnfhodhsvb", "j9"); } }
|
输出:
找到匹配字符串,起始:4 终止:5
KMP
哇,真是大学时的噩梦啊(笑),字符串匹配的最经典算法之一,曾被票选为当今世界最伟大的十大算法之一。恩,先回到正题吧,不闲扯了,KMP 算法我看了一下,觉得比较难的部分就是部分匹配值的计算了。BF 和 RK 在匹配不上时都是顺序向后移动一位继续匹配,而 KMP 不是,是按照计算的部分匹配值来向后移动。这里不具体解释原理,直说按照怎样的步骤去实现计算部分匹配值:
“部分匹配值”是指字符串前缀和后缀所共有元素的长度。前缀是指除最后一个字符外,一个字符串全部头部组合;后缀是指除第一个字符外,一个字符串全部尾部组合。以”ABCDABD”为例:
“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。
代码实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
|
public static int[] calcPartMatch(String keyword) { int[] partMatchVal = new int[keyword.length()];
for (int i = 0; i < keyword.length(); i++) { if (i == 0) { partMatchVal[0] = 0; continue; } String subKey = keyword.substring(0, i + 1); list1.clear(); for (int j = 1; j < subKey.length(); j++) { list1.add(subKey.substring(0, j)); }
list2.clear(); for (int j = 1; j < subKey.length(); j++) { list2.add(subKey.substring(j, subKey.length())); }
System.out.println("\ni = " + i);
for (String s : list1) { System.out.println("前缀:" + s); }
for (String s : list2) { System.out.println("后缀:" + s); }
list1.retainAll(list2); if (list1.size() == 0) partMatchVal[i] = 0; else { partMatchVal[i] = list1.get(0).length(); }
System.out.println("\n长度为:" + partMatchVal[i]); }
return partMatchVal; }
|
输入 ada ,输出:
1 2 3 4 5 6 7 8 9 10 11 12 13
| i = 1 前缀:a 后缀:d
长度为:0
i = 2 前缀:a 前缀:ad 后缀:da 后缀:a
长度为:1
|
计算得出的部分匹配值就是0、0、1
KMP算法实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
|
public static void kmpMatch(String originText, String keyword) { int[] partMatch = calcPartMatch(keyword);
for (int i = 0; i < originText.length(); ) { char c; int count = 0; for (int j = 0; j < keyword.length(); j++) { if (i + j >= originText.length()) break; c = originText.charAt(i + j); if (c != keyword.charAt(j)) { break; } count++; if (j == keyword.length() - 1) { System.out.println("找到匹配字符串,起始:" + i + " 终止:" + (i + keyword.length() - 1)); } } if (count == 0) { i++; } else { i += count - partMatch[count - 1]; } if (i > originText.length()) break; } }
|
输入:kmpMatch(“asdfasdfasdfasdfadae4rqerfasdfv”, “ada”);
输出:找到匹配字符串,起始:16 终止:18
这里还有 BM 和 Sunday 没有实现。