KMP算法(C)

一、简介:
  KMP用于检测模式串是否在主串当中,比如"aba"是否在 “cbaba"中。
  相比较于傻瓜式一对一匹配,KMP优点在于减少了指针的回溯,即最大程度利用了已经匹配的信息。比如模式串"abbabbc"和主串"abbabbdabbabbc"一一对比时在第七个字符(即索引[6])处匹配失败后,指针本应回溯至"abbabbdabbabbc"第二个字符处与模式串"abbabbc"重新开始匹配,而KMP则能让模式串第四个字符(即索引[3])与主串当前匹配失败处继续匹配,使得匹配速度大大提升。而匹配失败后具体应该模式串第几个字符与主串继续匹配呢,便是通过模式串所求每个字符的next值所决定。
  next值为模式串最长公共前后缀,如"aaab”,第一个字符next值为-1(因为他前面无字母),第二个为0(因为他前面只有一个字母,最长公共前后缀不能为本身),第三个为1(他前面有两个字母"aa",公共前后缀为a和a,故为1),第四个为2(他前面有三个字母"aaa",最长公共前后缀不能为本身,故最长公共前后缀应为第一个第二个字母"aa"和第二个第三个字母"aa")。
KMP算法的匹配方法
  KMP看似复杂,实则在了解完后方知其思路简单无比。

二、步骤:
  1.求得模式串每个字符的next值,即此字符前的最大公共前后缀长度值。
  2.依次匹配直到匹配失败。
  3.模式串后移n位,即将模式串失败处位置与最长公共前缀后的一个字母进行比较,n根据匹配失败处next值求得。
  4.重复2,3步骤直至匹配成功或者溢出。

三、代码:
  这段代码比较简单,因为使用的是数组分配空间,也就是说一开始输入串就被预定大小了,这么做的缺点便是容易输入溢出,每次需要根据输入量调整输入串大小。我也尝试过动态开辟内存以及链表形式,看起来比较冗杂,但这样就避免模式串或匹配串输入时超出预定值,使得代码更加健壮。以下为数组版本。

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

#pragma warning(disable:4996)//在我这个环境中,不添加这个忽略则scanf会出警告

#define MAX 100

typedef struct MSC {//定义模式串和匹配串结构体
	char data[MAX];
	int length;
}mscnode;

void GetNext(mscnode t, int next[])//t为模式串,将模式串当前所指值反复与下一个比较,K为累计匹配值,
{
	int j, k;
	j = 0; k = -1; next[0] = -1;
	printf("next数组为\n%d", next[0]);

	while (j<t.length - 1)
	{
		if (k == -1 || t.data[j] == t.data[k])
		{
			j++; k++;
			next[j] = k;
			printf(",%d", k);
		}
		else k = -1;
	}
}

int KMPIndex(mscnode s, mscnode t)//s为模式串,t为匹配串
{
	int next[MAX];
	int i = 0, j = 0;
	GetNext(t, next);
	while (i<s.length && j<t.length)
	{
		if (j == -1 || s.data[i] == t.data[j])
		{
			i++;
			j++; //i、j各增1
		}
		else j = next[j]; //i不变,j后退
	}

	if (j >= t.length)
		return(i - t.length); //返回匹配模式串的首字符下标
	else
		printf("\n匹配失败");
		return(-1); //返回不匹配标志
}

int main() {
	mscnode s, t;//最长不超过100字符
	printf("请输入目标串:");
	scanf("%s", s.data);
	s.length = strlen(s.data);//获取目标串长度
	printf("请输入模式串:");
	scanf("%s", t.data);
	t.length = strlen(t.data);//获取匹配串长度
	int c;
	c = KMPIndex(s, t);
	printf("\n匹配成功,索引范围为\n%d---%d\n", c,c+t.length-1);

	return 0;
}

四、结果:
运算结果
五、笔记:
  1.sizeof计算的是对象所占内存,而数据类型占内存的位数实际上与操作系统的位数和编译器(不同编译器支持的位数可能有所不同)都有关 ,GCC编译器中

C类型 32 64
char 1 1
short int 2 2
int 4 4
long int 4 8
long long int 8 8
char* 4 8
float 4 4
double 8 8

  需要说明一下的是指针类型存储的是所指向变量的地址,所以指针是全字长的,所以32位机器只需要32bit,而64位机器需要 64bit 。

  2.(1)表达式中必须包含常量值,包括内存分配,分配内存时需要指定内存具体大小,比如不能将参数作为声明的数组的长度,否则就需要动态分配内存。
在C中动态开辟空间需要用到三个函数:malloc(), calloc(), *realloc()*这三个函数都是向堆中申请的内存空间。在堆中申请的内存空间不会像在栈中存储的局部变量一样,函数调用完会自动释放内存,需要我们使用free()函数来手动释放。
  (2)何为栈内存和堆内存?栈内存存储的都是局部变量,凡是定义在方法中的都是局部变量(方法外的是全局变量),是先加载函数才能进行局部变量的定义,所以方法先进栈,然后再定义变量,变量有自己的作用域,一旦离开作用域(即方法函数已经出栈),变量就会被释放(必定变量需要更早出栈,从而失效)。栈内存的更新速度很快,因为局部变量的生命周期都很短。而堆内存存储的是对象,凡是new建立的都是在堆中,堆中存放的都是实体(对象),实体用于封装数据,如果一个数据消失,这个实体并没有消失,所以堆是不会随时释放的。堆里的实体虽然不会被释放,但是会被当成垃圾,Java有垃圾回收机制不定时的收取。
  (3)malloc分配的内存空间在逻辑上是连续的,而在物理上不连续。我们作为程序员,关注的是逻辑上的连续,其他的操作系统会帮着我们处理。 同时记住free()后一定要将指针赋值为NULL防止产生野指针。
  (4)我吐了,写动态分配内存版本时,malloc申请的堆内存在使用中不小心越界,调试时就一直给我在free那里报个中断,(我还贼心大在CFREE5上强行运行了一次,还成功了。。。弄得我一直以为是VS2015这个大环境的问题)跟踪存储单元才发现是越界?WTF?所以malloc申请的堆内存块大小一定要确保存储单元够用。爱情好像就是这样,悄然中行为越界导致爱情萌芽被掐断,你还不自知,还以为是大环境的问题,所以追求爱情前请先准备好足够的存储空间,比如包容的心、足够的面包等。

发布了4 篇原创文章 · 获赞 0 · 访问量 69

猜你喜欢

转载自blog.csdn.net/qq_40285768/article/details/104300371