KMP算法
简介
KMP算法是一种改进的字符串匹配算法,KMP算法的核心是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是通过一个next()函数实现,函数本身包含了模式串的局部匹配信息。KMP算法的时间复杂度O(m+n)。
示例
比如:现有字符串 str1 = ‘ABABDABACDABABCABAB’, 字符串 str2 = ‘ABABCABAB’, 计算 str2 字符串在 str1 中出现的起始下标。
解法1:暴力匹配
使用暴力匹配
// 实现一个简单的字符串搜索算法(返回str2在str1中第一次出现的下标)
public static int indexOf(String str1, String str2) {
if (str1 == null || str2 == null || str2.length() > str1.length()) {
return -1; // str2 不可能在 str1 中
}
for (int i = 0; i <= str1.length() - str2.length(); i++) {
int j;
for (j = 0; j < str2.length(); j++) {
if (str1.charAt(i + j) != str2.charAt(j)) {
break; // 一旦发现字符不匹配,立即跳出内部循环
}
}
if (j == str2.length()) {
return i; // 找到str2在str1中的起始下标
}
}
return -1; // 没有找到str2在str1中的匹配项
}
public static void main(String[] args) {
String txt = "ABABDABACDABABCABAB";
String pat = "ABABCABAB";
System.out.println("Index: " + indexOf(txt, pat)); // 应输出 "Index: 10"
}
结果:Index: 10
暴力匹配是使用双循环匹配,最大的问题是效率低下,实现简单,
问题:使用暴力匹配会发现重复匹配,比如 str2 匹配到下标4和str1 不匹配时,又会重str1的下标1 开始匹配。
解法2:KMP 算法
/**
* 计算匹配字符串的复用前后缀下标
* 比如字符串:ABABCABAB
* 那么返回值:[0,0,1,2,0,1,2,3,4]
* 描述:这个下标数组的作用就是当匹配到指定下标失败的时候,根据下标的值进行向后匹配,而不用重复匹配,
* 比如字符串匹配到下标为3失败时,则取数组下标为(3 - 1)的值1,然后从下标为1继续向后匹配
* @param pattern
* @return
*/
public static int[] computePrefixFunction(String pattern) {
int m = pattern.length();
int[] longestPrefixSuffix = new int[m];
int len = 0; // 长度为0的前后缀匹配的长度
longestPrefixSuffix[0] = 0;
for (int i = 1; i < m; i++) {
while (len > 0 && pattern.charAt(len) != pattern.charAt(i)) {
len = longestPrefixSuffix[len - 1];
}
// 如果值相同,标识可以复用,记录下标
if (pattern.charAt(len) == pattern.charAt(i)) {
len++;
}
// 将复用下标记录到对应的下标中
longestPrefixSuffix[i] = len;
}
return longestPrefixSuffix;
}
public static int KMPSearch(String text, String pattern) {
int n = text.length();
int m = pattern.length();
int[] lps = computePrefixFunction(pattern);
System.out.println("lps:"+JSON.toJSONString(lps));
int i = 0; // 文本字符串的索引,当前匹配到的索引
int j = 0; // 模式字符串的索引,当前匹配到的索引
while (i < n) {
// 根据下标判断值是否相同,相同的话,元数据下标和匹配数组下标自增
if (pattern.charAt(j) == text.charAt(i)) {
j++;
i++;
}
// 如果当前已匹配下标等于字符串的下标表示匹配完了,直接返回
if (j == m) {
return i - j; // 找到模式在文本中的位置,返回位置的开始索引
} else if (i < n && pattern.charAt(j) != text.charAt(i)) {
// 不匹配的情况,说明下次循环不匹配,如果j!=0,就需要重新计算匹配的下标
if (j != 0) {
j = lps[j - 1];
} else {
// j=0,标识,匹配的字符串需要从头计算
i = i + 1;
}
}
}
return -1; // 如果模式不在文本中出现,返回-1
}
public static void main(String[] args) {
String txt = "ABABDABACDABABCABAB";
String pat = "ABABCABAB";
System.out.println(pat);
System.out.println("Index: " + KMPSearch(txt, pat)); // 应输出 "Index: 10"
}
KMP算法主要是将匹配的字符串做复用下标处理,然后在匹配的时候,如果当前匹配不到,则根据复用下标处理。