一、简介:
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看似复杂,实则在了解完后方知其思路简单无比。
二、步骤:
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申请的堆内存块大小一定要确保存储单元够用。爱情好像就是这样,悄然中行为越界导致爱情萌芽被掐断,你还不自知,还以为是大环境的问题,所以追求爱情前请先准备好足够的存储空间,比如包容的心、足够的面包等。