KMP算法----代码实现篇

通过学习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浅显的理解,有不足之处希望大佬指正。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章