看了灯笼的讲解,形象生动的体会了KMP算法的奥妙,参考其思路用C++写了下来,关于KMP算法的讲解在代码注释中。
我按照自己的理解把算法的分为以下步骤(text为长字符串,pattern为和text匹配的短字符串):
- 列出pattern所有的前缀。比如“ABABCAABAB”,能够列出10种前缀(前缀的具体定义可以参照龙书第二章),
- 构建前缀表。找出每个前缀中前后能够匹配的字符串长度,例如“ABABCAAB”,只有首尾"AB"能够匹配,所以前缀表相应位置为2。
- 开始进行字符串的匹配。简单说,字符串匹配的时候,text和pattern的下标都加1(pattern长度等于1时只移动text的下标);如果不匹配,则根据前缀表进行pattern的移动再从移动后的位置开始匹配;
#include<stdio.h>
#include<iostream>
#include<string>
#include<stdlib.h>
using namespace std;
// 求前缀表
void prefix_table(char pattern[], int prefix[], int n) {
// 一开始规定好的
prefix[0] = 0;
int len = 0;
// 检测第i个字母
int i = 1;
// 主要讲第一次循环,如果i=1,len=0且pattern[0]!=pattern[1]的时候,当不匹配时我们会侧向选择,即 len = prefix[len-1]
// 但是在字符串的开头 prefix[len-1]是会越界的,所以应该直接让prefix[i] = len就好,然后继续向前匹配
while (i < n) {
if (pattern[i] == pattern[len]) {
len++;
prefix[i] = len;
i++;
}
else {
if (len > 0) {
len = prefix[len - 1];
}
else {
prefix[i] = len;
i++;
}
}
}
}
// 前缀表移动
void move_prefix_table(int prefix[], int n) {
// 生成的前缀表并不是我们想要的前缀表,我们要把前缀表有的向后移动,然后prefix[0]=-1
for (int i = n-1; i>0; i--) {
prefix[i] = prefix[i - 1];
}
prefix[0] = -1;
}
// KMP算法
void kmp_search(char text[], char pattern[]) {
int n = strlen(pattern);
int* prefix = new int[n];
prefix_table(pattern,prefix, n);
move_prefix_table(prefix, n);
int j = 0;
int i = 0;
int m = strlen(text);
// 做好前期各个变量的准备,主要是text,pattern的长度以及i,j两个下标
//text[i] len(text) = m;
// pattern[j] len(pattern) = n;
// 开始遍历text
while (i < m) {
// 如果j到了pattern的尽头且最后一位也匹配了,说明当前位置就是匹配到的字符串
if (j == n-1 && pattern[j] == text[i]) {
printf("Find pattern at %d\n", i - j);
// 为了匹配所有的pattern,设置j = prefix[j]继续匹配
j = prefix[j];
if (j == -1) {
j = 0;
}
}
// 如果字符匹配,直接ij向后移动.再考虑一种情况,如果当前匹配了,但是pattern长度为1,如都i++,j++那么j的读取会越界
// 所以我们要加一个判断,如果j==n-1,则只移动i,这是针对pattern为1的情况
if (text[i] == pattern[j]) {
if (j == n - 1) {
i++;
}
else {
i++; j++;
}
}
// 如果当前ij下pattern和text不匹配
else {
// 则令 j = prefix[j]
j = prefix[j];
// 如果这时候在字符串的开头,两者都不匹配的情况,只能将pattern向text的下一位匹配,即i,j各加1
if (j == -1) {
i++; j++;
}
}
}
}
int main() {
//第一个样例,匹配的字符串长度为1
char pattern[] = "A";
char text[] = "ABABABCABAABABABAB";
kmp_search(text, pattern);
// 第二个测试样例,匹配的字符串长度大于1
char pattern1[] = "AB";
kmp_search(text,pattern1);
system("pause");
return 0;
}