KMP算法!
KMP算法(Knuth-Morris-Pratt算法)是一种用于字符串匹配的高效算法。它的核心思想是通过预处理模式字符串(pattern),构建一个部分匹配表(也称为“失败函数”或“前缀函数”),从而在匹配过程中避免不必要的回溯。KMP算法的时间复杂度为O(n + m),其中n是文本字符串的长度,m是模式字符串的长度。
KMP算法的核心思想
-
部分匹配表(Prefix Table):
-
部分匹配表记录了模式字符串中每个位置的最长相同前缀和后缀的长度。
-
例如,对于模式字符串
"ABABC"
,其部分匹配表为[0, 0, 1, 2, 0]
。
-
-
匹配过程:
-
在匹配过程中,当字符不匹配时,KMP算法利用部分匹配表跳过一些字符,避免从头开始匹配。
-
KMP算法的步骤
-
构建部分匹配表:
-
初始化一个数组
prefix
,长度为模式字符串的长度。 -
使用两个指针,一个指向模式字符串的开头(
i
),另一个用于遍历模式字符串(j
)。 -
如果字符匹配,
prefix[j] = i + 1
,并移动两个指针。 -
如果不匹配,且
i > 0
,则将i
回退到prefix[i - 1]
。 -
如果不匹配,且
i == 0
,则prefix[j] = 0
,并移动j
。
-
-
匹配文本字符串:
-
使用两个指针,一个指向文本字符串(
i
),另一个指向模式字符串(j
)。 -
如果字符匹配,移动两个指针。
-
如果不匹配,且
j > 0
,则将j
回退到prefix[j - 1]
。 -
如果不匹配,且
j == 0
,则移动i
。
-
public class KMP {
// 计算部分匹配表(前缀函数)
private static int[] computePrefix(String pattern) {
int[] prefix = new int[pattern.length()];
int j = 0; // 指向模式字符串的前缀
for (int i = 1; i < pattern.length(); i++) {
while (j > 0 && pattern.charAt(i) != pattern.charAt(j)) {
j = prefix[j - 1]; // 回退到前一个匹配位置
}
if (pattern.charAt(i) == pattern.charAt(j)) {
j++; // 匹配成功,移动 j
}
prefix[i] = j; // 记录当前位置的最长前缀
}
return prefix;
}
// KMP 搜索算法
public static int kmpSearch(String text, String pattern) {
int[] prefix = computePrefix(pattern);
int j = 0; // 指向模式字符串
for (int i = 0; i < text.length(); i++) {
while (j > 0 && text.charAt(i) != pattern.charAt(j)) {
j = prefix[j - 1]; // 回退到前一个匹配位置
}
if (text.charAt(i) == pattern.charAt(j)) {
j++; // 匹配成功,移动 j
}
if (j == pattern.length()) {
return i - j + 1; // 返回匹配的起始位置
}
}
return -1; // 未找到匹配
}
public static void main(String[] args) {
String text = "ABABDABACDABABCABAB";
String pattern = "ABABCABAB";
int index = kmpSearch(text, pattern);
if (index != -1) {
System.out.println("Pattern found at index: " + index);
} else {
System.out.println("Pattern not found.");
}
}
}
示例输出
对于文本字符串 "ABABDABACDABABCABAB"
和模式字符串 "ABABCABAB"
,输出结果为:
Pattern found at index: 10
优点
-
高效:KMP算法避免了不必要的回溯,提高了匹配效率。
-
适用性广:适用于各种字符串匹配场景,尤其是模式字符串较长的情况。
缺点
-
实现复杂:相比暴力匹配算法,KMP算法的实现较为复杂。
-
空间开销:需要额外的空间存储部分匹配表。
KMP算法是字符串匹配领域的重要算法,理解其原理和实现对于解决相关问题非常有帮助。