说完了KMP算法的一些基本的操作,我们再来说一下KMP算法的代码实现:
写代码的时候和上面我们所说的操作都是一样的:
首先我们需要完成的就是prefix-table的构建,为了大家能更好的理解,我们使用一串新的字符串作为例子进行讲解:
我们写出它所有的前缀和最长公共前后缀的长度为:
我们从中可以发现一个规律:
例如:我们拿字符串:“A B A B C A”和字符串:“A B A B C A B”举例
第一段字符串的公共前后缀的长度为1 ,也就是说第一个和最后一个肯定是相等的,那么如果我们要使得其公共前后缀长度增加的话,我们要在最后一位添加一个和第二个字母相等的字母,也就是说我们的字符串“A B A B C A”,如果想让它的最长公共前后缀的长度变为2,就需要在A的后面加上一个B。
同理,如果字符串“A B A B C A B”也想让自己的长度+1,它的最前面两个字符串是A B,最后面两个字符串也是A B,也就得在字符 B 的后面加上和前面第二位B后面相同的字符,也就是A,这样就得到了最长公共前后缀长度为3 的字符串“A B A B C A B A”,所以我们可以看出,每一个字符串和每一个字符串之间是有关系的,这样的关系就可以 指导我们填写prefix-table和编写代码:
还是拿上面的那个字符串来进行举例:
假如我们现在要填写6号下标的最长公共前后缀的长度,我们就可以是使用绿线以前的信息作为参考:
那么我们可以从前面的字符串得到什么信息呢:前面的字符串是“A B A B C A”,并且5号下标的最长公共前后缀的长度是1 ,也就是说,前面的字符串只有一个A和最后一个A是相同的,所以如果要让6号下标的数字变成2,唯一的办法就是看6号下标的字符是否和1号下标的字符相等,也就是我们只需检测6号下标的字符是否为B。我们设len表示达到的最长公共前后缀的长度,i标售当前要比较的字符,所以我们就需要判断P[len] == P[i],如果相等的话,我们就可以让len++,并且让prefix[i] = len;那如果匹配不上的话该怎么办,我们可能会想:直接填0就好了。直接填0 是没错,但是它并不适用于最后一个字母,
就如上面的情况,最后一个匹失败,正确结果是1,填0就会出错。这里解决的办法就是将len向后移动一位,将当前len的prefix里面的长度作为新的len进行比较也就是len = prefix[len-1],这个办法还是存在一个小的问题,有可能会造成数组越界,因为都是向后prefix数组向后走的,所以还需要再判断一次。这样,我们的prefix-table就构造好了,下面只需要将每一位进行后移,第一位填写-1。
void prefix_table(char pattern[], int prefix[], int n) {
prefix[0] = 0; //前缀表的第0位一定为0
int len = 0; //比较的长度,初始值为0
int i = 1; //检测第i个字母,从第1个字母开始比较
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++;
}
}
}
下面是完整的代码:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
/*pattern : 子串 prefix : 前缀表 n : 前缀表的长度 */
void prefix_table(char pattern[], int prefix[], int n) {
prefix[0] = 0; //前缀表的第0位一定为0
int len = 0; //比较的长度,初始值为0
int i = 1; //检测第i个字母,从第1个字母开始比较
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)
{
for (int i = n; i > 0; i--)
{
prefix[i] = prefix[i - 1];
}
prefix[0] = -1;
}
/*传参:text:主串
* pattern: 待匹配字符串
* */
void KMP_Search(char text[], char pattern[])
{
bool flag = true;
int n = (int)strlen(pattern);//待匹配字符串长度
int m = (int)strlen(text);//主串字符串长度
int i = 0; //定位主串text[i]
int j = 0; //定位待匹配串pattern[j]
//动态申请内存作为前缀表
int * prefix = (int *)malloc(sizeof(int)*n);
//生成前缀表
prefix_table(pattern, prefix, n);
//移动前缀表
move_prefix_table(prefix, n);
//进行匹配操作
while (i < m)
{
if (j == n - 1 && text[i] == pattern[j])
{
flag = false;
printf("find pattern at index:%d\n", i - j);
j = prefix[j];
}
if (text[i] == pattern[j])
{
i++;
j++;
}
else
{
j = prefix[j];
if (j == -1)
{
i++;
j++;
}
}
}
if (flag)
{
printf("can't find the pattern!\n");
}
}
int main()
{
char pattern[] = "abcde";
char text[] = "ababcabcdeacbab";
KMP_Search(text, pattern);
return 0;
}
运行测试代码也是没有问题的: