通过学习kmp的思想和实现步骤,我们已经初步理解了kmp算法的运行过程,那么接下来必然要学会代码实现。现在,进行代码实现吧!
kmp主要就是两步:
第一步,求出前缀表,也就是常说的next数组;
第二步,进行具体的匹配过程。
首先,我们一起来用代码解决kmp算法中的前缀表吧!
我们再次回顾前缀表的构建方法(请移步到我们上一篇博客: KMP算法—讲解篇)。
这次我们会和上次讲的前缀表建立有一点点不同(但其实就是换汤不换药,我会慢慢说明的):
我们可以对比一下临近的两行,比如:
在此,假如我们已经获得了上图第一行的前缀表数据(当前是1),那么我们只需判断下一位B(p[6])是否和B(p[1])相同,如果相同的话,那自然而然这一行对应的前缀表数据就是1++了;如果不相同呢?
我们暂且不讨论不同的情况(狗头保命),我们先把现有的思路实现了。
//pattern是我们现有的模式串,prefix是我们要求的初代前缀表(因为没有移动),n是串的长度
void prefix_table(char pattern[],int prefix[],int n){
prefix[0]=0;//不解释(只有一个元素的串嘛,翻一翻上面的那个每行对应的数字奥)
int len =0;//len,是用来比较的长度,具体作用当你模拟几次就会了解。
int i=1;//用来标是当前所求的位置下标
while(i<n){//i<n的时候循环
if(pattern[i]==pattern[len]){
//len就是上一次求完之后的公共串长度,判断pattern[len]和现在位置是否相同
len++;//长度加1
prefix[i]=len;//当前的前缀表数据就是加1之后的len
i++;//往后移动一位
}
else {
//这里就是我们发现两个不相同时的情况。
}
}
}
下来,我们讨论不相同时的情况。
看到这,可能就有疑问了:不是说这些都要往后移动一格吗?第一个不是添加-1吗?
这就是我在开头所说的那一点点不同(其实都是一样的),在这里用代码实现前缀表的过程中,我们先像这样对齐的建立,再都往后移动一格,就好了。
好了我们继续回到问题。
我们发现此时的len=3,i=8(假如我们已经进行到当前位置了)。明显pattern[3]和pattern[8]不相同,那么我们把len更新为prefix[len-1];len=1再次对比,发现pattern[len]和pattern[8]不相同,我们继续把len更新为prefix[len-1],len=0;发现pattern[len]和pattern[8]相同,所以len++,prefix[8]=1;
完善代码(包含移位的过程):
//pattern是现有的模式串,prefix是要求的初代前缀表(因为没有移动),n是串的长度
void prefix_table(char pattern[],int prefix[],int n){
prefix[0]=0;//不解释(只有一个元素的串嘛,翻一翻上面的那个每行对应的数字奥)
int len =0;//len,是用来比较的长度,具体作用模拟几次就会理解。
int i=1;//用来标是当前所求的位置下标
while(i<n){//i<n的时候循环
if(pattern[i]==pattern[len]){
//len就是上一次求完之后的公共串长度,判断pattern[len]和现在位置是否相同
len++;//长度加1
prefix[i]=len;//当前的前缀表数据就是加1之后的len
i++;//往后移动一位
}
else {
//这里就是我们发现两个不相同时的情况。
//为了防止出现我们求prefix[1]的时候访问到pattern[-1]
if(len>0){
len = prefix[len-1];
//正常情况错位更新len(因为是到了len-1嘛,所以称为len-1)
}
else {
prefix[i]=len;//这里len就是0,所以写成0也是可以的
i++;
}
}
}
//现在实现移位
for(i=n-1;i>0;i--){
prefix[i]=prefix[i-1];
}
prefix[0]=-1;
}
到这里,已经简述了前缀表的建立,下面就是完整kmp算法的实现:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
//pattern是现有的模式串,prefix是要求的初代前缀表(因为没有移动),n是串的长度
void prefix_table(char pattern[],int prefix[],int n){
prefix[0]=0;//不解释(只有一个元素的串嘛,翻一翻上面的那个每行对应的数字奥)
int len =0;//len,是用来比较的长度,具体作用模拟几次就会理解。
int i=1;//用来标是当前所求的位置下标
while(i<n){//i<n的时候循环
if(pattern[i]==pattern[len]){
//len就是上一次求完之后的公共串长度,判断pattern[len]和现在位置是否相同
len++;//长度加1
prefix[i]=len;//当前的前缀表数据就是加1之后的len
i++;//往后移动一位
}
else {
//这里就是我们发现两个不相同时的情况。
//为了防止出现我们求prefix[1]的时候访问到pattern[-1]
if(len>0){
len = prefix[len-1];//正常情况错位更新len(因为是到了len-1嘛,所以称为len-1)
}
else {
prefix[i]=len;//这里len就是0,所以写成0也是可以的
i++;
}
}
}
//现在实现移位
for(i=n-1;i>0;i--){
prefix[i]=prefix[i-1];
}
prefix[0]=-1;
}
void kmp_search(char text[],char pattern[]){//主串text,模式串pattern
int n=strlen(pattern);
int m=strlen(text);
int *prefix=(int *)malloc(sizeof(int)*n);//建立一个前缀表数组,也即是next数组
prefix_table(pattern,prefix,n);//调用建立前缀表数组函数
int i=0,j=0,flag=0;
//text[i] ,len=m 用i和m表示主串的下标和长度
//pattern[j] ,len=n 用j和n表示模式串的下标和长度
while(i<m){
if(j==n-1&&text[i]==pattern[j]) {
printf("We find it in %d\n",i-j);//输出模式串在主串中的位置
flag=1;
j=prefix[j];//匹配成功 ,我们可以直接退出,当然有些主串和模式串并不只在一个地方包含
}
if(text[i]==pattern[j]){//正常匹配,往后+就行
i++;
j++;
}
else {//匹配失败,失配位和前缀表中数据所指的项对齐
j=prefix[j];
if(j==-1){
//这就是我们访问到了那个开头的-1,这个时候,我们当然是让模式串往后移动一格,并且和主串的下一个元素匹配
i++;
j++;
}
}
}
if(flag==0){
printf("We can't find it in text\n");//flag=0,未找到
}
}
int main()
{
char pattern[]="ABABCABAA";
char text[]="ABABABCABAABABABABCABAA";
//char text[]="ABABABCAAABABABABCAAA";
kmp_search(text,pattern);
return 0;
}
以上就是本人对KMP浅显的理解,有不足之处希望大佬指正。