KMP算法原理
KMP算法的核心思想是利用已经匹配过的部分信息,避免不必要的字符比较,从而提高搜索效率。它通过预处理模式字符串,生成一个部分匹配表(也称为“失败函数”表或“跳转表”),来指导搜索过程中的字符比较。
1. 部分匹配表(Partial Match Table, PMT)
部分匹配表是一个数组,用于存储模式字符串中每个前缀的最长相同前后缀的长度(不包括整个前缀本身)。这个数组在算法开始之前通过预处理模式字符串来生成。
例如,对于模式字符串"ABCDABD"
:
- 前缀A的最长相同前后缀长度为0(没有前后缀)
- 前缀AB的最长相同前后缀长度为0(没有前后缀)
- 前缀ABC的最长相同前后缀长度为0(没有前后缀)
- 前缀ABCD的最长相同前后缀长度为0(没有前后缀)
- 前缀ABCDA的最长相同前后缀长度为1(A)
- 前缀ABCDAB的最长相同前后缀长度为2(AB)
- 前缀ABCDABD的最长相同前后缀长度为0(没有更长的前后缀)
因此,部分匹配表为[0, 0, 0, 0, 1, 2, 0]
。
2. 搜索过程
在搜索过程中,我们维护两个指针,一个指向主字符串的当前位置(记作i
),另一个指向模式字符串的当前位置(记作j
)。我们从主字符串的第一个字符和模式字符串的第一个字符开始比较。
- 如果当前字符匹配(即
text[i] == pattern[j]
),则两个指针都向后移动一位,继续比较下一个字符。 - 如果当前字符不匹配(即
text[i] != pattern[j]
),则根据部分匹配表将模式字符串的指针j
回退到pmt[j-1]
的位置(如果j
为0,则直接将i
和j
都移动到下一个位置),然后再次比较当前字符。 - 如果模式字符串的指针
j
回退到了模式字符串的开头(即j == 0
),则将主字符串的指针i
向后移动一位,然后重新开始比较。 - 当模式字符串的指针
j
到达模式字符串的末尾时,说明找到了一个匹配项,记录该位置,并将两个指针都移动到下一个位置,继续搜索下一个匹配项。
3. 算法优势
KMP算法的优势在于,当发生字符不匹配时,它可以根据部分匹配表直接跳过主字符串中一些不可能包含匹配项的位置,从而避免了不必要的字符比较。这使得KMP算法在处理大量数据时具有更高的效率。
总结
KMP算法通过预处理模式字符串生成部分匹配表,并在搜索过程中利用这个表来指导字符比较,从而避免了不必要的比较操作,提高了搜索效率。它在处理字符串匹配问题时具有广泛的应用。
代码实现
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <string.h>
#include <iostream>
using namespace std;
// 暴力求解法
int bruteForce(char* txt, char* pat) {
int n = strlen(txt);
int m = strlen(pat);
int i, j;
for (i = 0; i <= n-m; i++) {
for (j = 0; j < m; j++) {
if (txt[i + j] != pat[j]) {
break;
}
}
if (j == m) {
return i;
}
}
return -1;
}
// 构建next数组
void computeNextArray(char* pat, int M, int* Next) {
int j = 0;
Next[0] = 0;
for (int i = 1; i < M; i++) {
// 前后缀不匹配
while (j > 0 && pat[i] != pat[j]) {
j = Next[j-1];
}
// 前后缀匹配
if (pat[i] == pat[j]) {
j++;
}
Next[i] = j;
}
}
// KMP匹配算法
int KMPSearch(char* pat, char* txt) {
int M = strlen(pat);
int N = strlen(txt);
int* Next = (int*)malloc(M * sizeof(int));
computeNextArray(pat, M, Next);
int i = 0; // 文本串指针
int j = 0; // 模式串指针
while (i < N) {
if (pat[j] == txt[i]) {
j++;
i++;
}
if (j == M) {
return i - j; // 匹配成功
}
else if (i < N && pat[j] != txt[i]) {
if (j != 0) {
j = Next[j - 1];
}
else {
i++;
}
}
}
return -1; // 未找到匹配
}
int main() {
char txt[1000] = { 0 };
char pat[1000] = { 0 };
fgets(txt, 1000, stdin); // 获取一个字符串
int length = strlen(txt);
txt[length - 1] = '\0';
fgets(pat, 1000, stdin); // 获取模式串
length = strlen(pat);
pat[length - 1] = '\0';
cout << "暴力求解:" << bruteForce(txt,pat) << endl;
cout << "KMP求解:" << KMPSearch(pat, txt) << endl;
return 0;
}