简介:
背景
KMP 算法由计算机科学家 Donald Knuth、Vaughan Pratt 和 James H. Morris 在 1977 年共同提出,目的是提高字符串匹配的效率。在字符串匹配问题中,给定一个主字符串和一个模式字符串,目标是找出模式字符串在主字符串中出现的位置。经典的朴素字符串匹配算法在遇到不匹配时,往往需要回退到主字符串的下一个字符位置,这导致了效率低下。
原理
KMP 算法的关键在于部分匹配表(也称为 LPS数组),通过利用模式字符串中已经匹配的部分信息,避免了不必要的重复比较。当模式串与主串不匹配时,可以直接跳过一些字符的比较,而不是重新从头开始。
部分匹配表(LPS 数组)
LPS 数组存储的是模式串中每个位置的最长可匹配前缀的长度。具体来说,LPS 代表的是模式串中从开头到当前位置的子串中,最左边的前缀和最右边的后缀最长的匹配长度。
如模式串 "ABABCA" 的 LPS 数组计算过程如下:
- LPS[0] = 0,因为没有前缀和后缀。
- LPS[1] = 0,因为 "A" 没有重复。
- LPS[2] = 1,因为前缀 "A" 和后缀 "A" 相同。
- LPS[3] = 2,因为前缀 "AB" 和后缀 "AB" 相同。
- LPS[4] = 0,因为 "ABC" 无法匹配。
- LPS[5] = 1,因为前缀 "A" 和后缀 "A" 相同。
最终,LPS数组为 [0, 0, 1, 2, 0, 1]。
算法步骤
-
构建 LPS 数组:
- 初始化一个长度为模式串长度的 LPS 数组,并设置第一个元素为 0。
- 遍历模式串,根据相等和不等的情况填充 LPS 数组。
-
进行匹配:
- 初始化主字符串和模式字符串的索引。
- 逐个进行字符比较:
- 如果当前字符相同,继续比较下一个字符。
- 如果全部匹配,则记录下匹配位置,并利用 LPS 数组更新模式字符串的索引。
- 如果不相等且已有匹配,则根据 LPS 数组更新模式字符串的索引,跳过已经匹配的部分;如果没有匹配,则移动主字符串的索引。
示例
假设主字符串为 "ABABDABACDABABCABAB" ,模式字符串为 "ABABCABAB"。
-
构建 LPS 数组:
- 对于模式串 "ABABCABAB",计算出 LPS 数组为 [0, 0, 1, 2, 0, 1, 2, 3, 4]。
-
匹配过程:
- 从主字符串的第一个字符开始与模式字符串的第一个字符进行比较。
- 依次比较,直到发现模式字符串在主字符串中有完整的匹配。
- 在匹配失败时,根据 LPS 数组的值调整模式字符串的起始匹配位置,而不是回到主字符串的下一个字符。
时间复杂度
KMP 算法的时间复杂度为 O(n + m),其中 n 是主字符串的长度,m 是模式字符串的长度。这是因为 KMP 通过 LPS 数组避免了在主字符串中重复检查已经比较过的部分。
优缺点
优点:
- 效率高:在处理大规模文本或需要多次查询匹配时,KMP 能显著减少时间消耗。
- 适用范围广泛:几乎可以应用于所有需要字符串匹配的场景,如搜索引擎、文本编辑器、数据分析等。
缺点:
- 实现相对复杂:LPS 数组的构建和匹配过程相比于简单的暴力匹配方法稍显复杂,不适合初学者。
- 对小样本的处理效率并不明显:对于短文本,KMP 相比于其他方法,如简单的暴力匹配,其优势在执行时间上并不明显。
应用场景
KMP 算法广泛应用于以下领域:
- 文本编辑器:查找特定的字符串或关键字。
- 搜索引擎:快速匹配并定位网页中的关键词。
- DNA 序列分析:寻找 DNA 片段中的特定序列。
- 数据挖掘:处理大量文本数据,进行模式匹配和分析。
总结
KMP算法是解决字符串匹配问题的一种高效算法,通过利用部分匹配表避免了重复比较,显著提高了匹配效率。它在信息检索、文本处理等领域有着广泛的应用,是算法与数据结构中的经典内容之一。
代码示例:
Python:
def kmp_search(text, pattern):
# 生成部分匹配表
def compute_lps(pattern):
lps = [0] * len(pattern)
length = 0 # 长度为0的最长前缀后缀
i = 1
while i < len(pattern):
if pattern[i] == pattern[length]:
length += 1
lps[i] = length
i += 1
else:
if length != 0:
length = lps[length - 1]
else:
lps[i] = 0
i += 1
return lps
lps = compute_lps(pattern)
i = j = 0 # i为text的索引,j为pattern的索引
while i < len(text):
if pattern[j] == text[i]:
i += 1
j += 1
if j == len(pattern):
print(f"模式串在主串中出现,起始位置: {i - j}")
j = lps[j - 1]
elif i < len(text) and pattern[j] != text[i]:
if j != 0:
j = lps[j - 1]
else:
i += 1
# 示例使用
text = "ababcabcabababd"
pattern = "ababd"
kmp_search(text, pattern)
代码说明:
compute_lps函数用于构建部分匹配表。kmp_search函数用于在主字符串中搜索模式字符串,并输出所有出现的位置。
c++:
#include <iostream>
#include <vector>
#include <string>
void computeLPSArray(const std::string &pattern, std::vector<int> &lps) {
int length = 0; // 最长前缀后缀的长度
lps[0] = 0; // lps[0] 总是 0
int i = 1;
// 计算 lps 数组
while (i < pattern.length()) {
if (pattern[i] == pattern[length]) {
length++;
lps[i] = length;
i++;
} else {
if (length != 0) {
length = lps[length - 1];
} else {
lps[i] = 0;
i++;
}
}
}
}
void KMPSearch(const std::string &text, const std::string &pattern) {
int M = pattern.length();
int N = text.length();
// 创建 lps 数组
std::vector<int> lps(M);
computeLPSArray(pattern, lps);
int i = 0; // 主字符串的索引
int j = 0; // 模式字符串的索引
while (i < N) {
if (pattern[j] == text[i]) {
i++;
j++;
}
if (j == M) {
std::cout << "模式串在主串中出现,起始位置: " << i - j << std::endl;
j = lps[j - 1]; // 重置模式字符串索引
} else if (i < N && pattern[j] != text[i]) {
if (j != 0) {
j = lps[j - 1]; // 继续查找
} else {
i++;
}
}
}
}
int main() {
std::string text = "ababcabcabababd";
std::string pattern = "ababd";
KMPSearch(text, pattern);
return 0;
}
代码说明:
- computeLPSArray:该函数用于计算模式字符串的最长前缀后缀数组(LPS数组)。
- KMPSearch:该函数用于在主字符串中搜索模式字符串,并输出所有出现的位置。
- main:在主函数中定义主字符串和模式字符串,并调用
KMPSearch函数进行搜索。
Pascal:
program KMPAlgorithm;
uses SysUtils;
procedure ComputeLPSArray(pattern: string; var lps: array of Integer);
var
length, i: Integer;
begin
length := 0; // 最长前缀后缀的长度
lps[0] := 0; // lps[0] 总是 0
i := 1;
// 计算 lps 数组
while i < Length(pattern) do
begin
if pattern[i + 1] = pattern[length + 1] then
begin
Inc(length);
lps[i] := length;
Inc(i);
end
else
begin
if length <> 0 then
length := lps[length - 1]
else
begin
lps[i] := 0;
Inc(i);
end;
end;
end;
end;
procedure KMPSearch(text, pattern: string);
var
lps: array of Integer;
i, j: Integer;
begin
SetLength(lps, Length(pattern));
ComputeLPSArray(pattern, lps);
i := 1; // 主字符串索引
j := 1; // 模式字符串索引
while i <= Length(text) do
begin
if pattern[j] = text[i] then
begin
Inc(i);
Inc(j);
end;
if j > Length(pattern) then
begin
WriteLn('模式串在主串中出现,起始位置: ', i - j);
j := lps[j - 2]; // 重置模式字符串索引
end
else if (i <= Length(text)) and (pattern[j] <> text[i]) then
begin
if j <> 1 then
j := lps[j - 2] // 继续查找
else
Inc(i);
end;
end;
end;
var
text, pattern: string;
begin
text := 'ababcabcabababd';
pattern := 'ababd';
KMPSearch(text, pattern);
end.
代码说明:
- ComputeLPSArray:该过程用于计算模式字符串的最长前缀后缀数组(LPS数组)。
- KMPSearch:该过程用于在主字符串中搜索模式字符串,并输出所有出现的位置。
- 主程序:定义了主字符串和模式字符串,并调用
KMPSearch进行搜索。
10万+

被折叠的 条评论
为什么被折叠?



