KMP(Knuth-Morris-Pratt)搜索算法是一种高效的字符串匹配算法,用于在一个主字符串(文本串)中查找一个子字符串(模式串)的出现位置。KMP 算法的核心优势在于它能够利用已匹配部分的信息来避免不必要的比较,从而提高搜索效率。
KMP 算法的基本原理
- 部分匹配表(Partial Match Table,PMT)或最长前缀后缀数组(LPS)
KMP 算法的关键在于预处理模式串,生成一个部分匹配表(PMT)或最长前缀后缀数组(LPS)。这个表记录了模式串中每个位置之前的子串的最大相同前缀和后缀的长度。
前缀:从模式串的开头到某个位置的子串。
后缀:从某个位置到模式串的结尾的子串。
最长相同前缀和后缀:前缀和后缀不能完全重叠,即前缀的最后一个字符不能是后缀的第一个字符。 - 计算 LPS 数组
LPS 数组的计算过程如下:
初始化 lps[0] = 0,因为单个字符没有前缀和后缀。
对于每个位置 i(从 1 到 m-1),计算 lps[i]:
如果 P[i] == P[lps[i-1]],则 lps[i] = lps[i-1] + 1。
如果 P[i] != P[lps[i-1]],则回溯到 lps[i-1] 的前一个位置,继续比较,直到找到匹配或回溯到 0。 - 搜索过程
在主字符串 T 中搜索模式串 P 时:
初始化两个指针 i 和 j,分别指向主字符串和模式串的当前比较位置。
逐字符比较 T[i] 和 P[j]:
如果匹配成功(T[i] == P[j]),则两个指针都向前移动。
如果匹配失败(T[i] != P[j]),则根据 LPS 数组调整模式串的指针 j,使其跳过已匹配的部分,避免重复比较。
如果模式串指针 j 达到模式串的长度 m,则表示找到匹配,返回匹配的起始位置。
#include <iostream>
#include <vector>
#include <string>
// build LPS array
std::vector<int> computeLPSArray(const std::string& pattern) {
int m = pattern.size();
std::vector<int> lps(m, 0);
int length = 0;
int i = 1;
while (i < m) {
if (pattern[i] == pattern[length]) {
length++;
lps[i] = length;
i++;
}
else {
if (length != 0) {
length = lps[length - 1];
}
else {
lps[i] = 0;
i++;
}
}
}
return lps;
}
// KMP search
std::vector<int> KMP(const std::string& text, const std::string& pattern) {
int n = text.size();
int m = pattern.size();
std::vector<int> lps = computeLPSArray(pattern);
std::vector<int> result;
int i = 0;
int j = 0;
while (i < n) {
if (pattern[j] == text[i]) {
j++;
i++;
}
if (j == m) {
result.push_back(i - j);
j = lps[j - 1];
}
else if (i < n && pattern[j] != text[i]) {
if (j != 0) {
j = lps[j - 1];
}
else {
i++;
}
}
}
return result;
}
int main() {
std::string text = "ABCABDABCACDABABCABCAB";
std::string pattern = "AB";
std::vector<int> result = KMP(text, pattern);
if (result.empty()) {
std::cout << "模式字符串未找到。" << std::endl;
}
else {
std::cout << "模式字符串在以下位置找到:";
for (int pos : result) {
std::cout << " " << pos;
}
std::cout << std::endl;
}
getchar();
return 0;
}