由于本人的失误,将next 记成了nest ,回过头来早已完成,所以请见谅,望包涵,希望大家也不要犯我这个错误。
本文章是继 上一篇我的文章 数据结构-字符串暴力匹配(超详细)学习笔记,在此基础上续写的KMP算法,我将详细介绍下标从1开始和下标从0开始两种情况下的nest值和匹配时的不同。
我学习kmp算法时,视频看的是两位哔哩哔哩up主的 数据结构-字符串暴力匹配 和 KMP算法之求next数组代码讲解 。之后总结他们两者的所讲的内容然后加上自己的分析,就完成了此篇文章。 我的文章有点长,希望你能够耐心看完,一定一定会有所收获的!
通过顺序存储方式进行初始化,结构体里定义了char型指针来代表数组的首地址,定义了length来确定串的长度。
//初始化字符串
typedef struct String
{
char* data;
int length;
}String;
定义一个字符串结构体指针,为其开辟空间,初始化数据。
//创建空串
String* initString()
{
String* s = (String*)malloc(sizeof(String));
s->data = NULL;
s->length = 0;
return s;
}
注意:函数中传进来的是字符串,而c语言中,没有字符串,仍是char 型数组 只不过末尾自带了\0,仅此而已!
void stringAssign(String* s, char* arr) //传进字符串
{
if (s->data) {
free(s->data); //释放其地址
}
int len = 0; //创建len 计算 数组的长度
char* temp = arr;
while (*temp) { //计算加入字符串的长度 注意: *temp 解引用 指向字符元素的ascll表值 指向\0则结束循环
temp++;
len++;
}
if (len == 0) {
s->data = NULL; //传来的字符串为空
s->length = 0;
}
else {
temp = arr;//将temp重新指向data的初地址
s->length = len;
s->data = (char*)malloc(sizeof(char) * (len + 1));//"\0"也占内存 注意: 是char * 型 并用s.data 首地址来接收
for (int i = 0; i <= len; i++, temp++) {
s->data[i] = *temp;//指针数组
}
}
}
若仍是有点迷糊,此代码详解在 数据结构-字符串暴力匹配(超详细)学习笔记 里,前三步和字符串暴力匹配是完全一样的。
// 公共前后缀 求 nest数组
int * getNest(String* s)
{
int* nest = (int*)malloc(sizeof(int) * s->length);
int i = 1;
int j = 0;
nest[i] = j;//nest[1] = 0; 先将公共前后缀不存在的首元素表示出来
while(i <= s->length)
{ //求哪一个 就看 前一个
if( j == 0 || s->data[i] == s->data[j]) //如果前一个 和 它当前的nest数组为下标的值相等
{
i++;
j++;
nest[i] = j; // 当前nest的值 = 前一个next值 + 1
}
else // 不相等,i 不变 变 j 继续比较 直到 相等 或者 j == -1
{
j = nest[j];
}
}
return nest;
}
原理:
代码解释:
1.由规律可知:nest数组上的值每次最多在前一个值的基础上+1,那么是如何判断+1的呢?
通过比较 前一个data数组的值(设为data[X]) 与它的nest值为下标的data数组(设为data[Y]) 是否相等,(即 data[X] == data[Y]) 相等 则 +1。
解释:
如:求 nset[4]的值,此时i = 3,j = 1,让data[3] 与 以nest[3] 为下标的 data[1] 进行比较 data[3] == data[1] 即 a == a, 相等 所以 i++,j++,nest[i] = j 即nest[4] = 2;
2.若前一个data数组的值(设为data[X]) 与 它的nest值为下标的data数组(设为data[Y])不相等?
data[X] 保持不变 ,求出 data[Y] 的 nest值为下标的data数组(设为data[Z]) , 继续让data[X]与data[Z]判断是否相等,相等即+1,若不相等,重复此操作,直到 j == 0,意味着:公共前后缀不存在,只能为通过条件判断 令当前nest值 = 1 了。
解释:
如:求 nset[7]的值,此时i = 6,j = 4,让data[6] 与 以nest[6] 为下标的 data[4] 进行比较,c != b, i不变,让 j = nest[4] = 2,让data[6] 与 data[2]进行比较,c != b, i 不变,再让 j = nest[2] = 1,让data[6] 与 data[1]进行比较,c != a, i 不变,再让 j = nest[1] = 0,此时 j == 0,通过条件判断,此时i++,j++,data[7] = 1。
不过在现实中,我们是需要将最后一位元素赋值的,这里只是证明了最后一位元素的nest值不受它本身的data值的影响才故意不赋值。
3.为什么while(i <= s->length)条件里面还要 “ = ” 呢? 一共有7个元素即长度为7,不应该只需要i = 6即可推出吗?
我的结论是:“ = ” 可加可不加。加了更加严谨,且仍然不影响后面的操作。
解释:
现在我们假设一共6位元素了,第7位元素就真的不存在了。那么给nest[6]赋完值时,i = 5,j =4。
注意:我们传进来的结构体字符串指针 s 的data域是 char型数组,并且 是字符串的形式。字符串在c语言中本质是char型数组,不过是末尾带了'\0'。
在c语言中字符串的长度不算上'\0'(即s->length == 6),但实际上char型数组的元素又包含了‘\0’在内。所以,最后继续i++,i = 6,j = 4,是可以继续给第7位 元素 '\0' 求出 nest[7] 的值的 即 nest[7] = 1 。
多求出来的第7位的nest值,是不会影响后续的KMP匹配的,因为当主串与子串进行匹配,匹配到最后时,他们都有‘\0’,仍然是匹配成功的状态!
下标从1开始,nest = 公共前后缀 + 1;当我们要实现模式串数组从下标0开始,就要将nest数组的值全部 - 1 即此时 nest = 公共前后缀。此时 nest[0] = -1,思路是与下标从1开始是完全一样。“ = ” 仍然 可加可不加。
// 公共前后缀 求 nest数组
int * GetNest(String* s)
{
int* nest = (int*)malloc(sizeof(int) * s->length);
int i = 0;
int j = -1;
nest[i] = j;
while (i <= s->length - 1)
{ //求哪一个 就看 前一个
if (j == -1 || s->data[i] == s->data[j]) //如果前一个 和 它当前的nest数组为下标的值相等
{
i++;
j++;
nest[i] = j; // 当前nest的值 = 前一个nest值 + 1
}
else // 不相等,i 不变 变 j 继续比较 直到 相等 或者 j == -1
{
j = nest[j];
}
}
return nest;
}
//kmp匹配
void kmpMatch(String* master, String * sub,int * nest)
{
int i = 0;
int j = 0;
while (i < master->length && j < sub->length)
{
if ( j == -1 ||master->data[i] == sub->data[j])
{
i++;
j++;
}
else
{
j = nest[j];
}
}
if (j == sub->length)
{
printf("KMP match success.\n");
}
else
{
printf("KMP match fail.\n");
}
}
代码解释:
跟字符串暴力匹配的做法仍是一样的,只不过是 增加了j == -1的条件 和将 暴力匹配中 i = i - j + 1; 换成了 j = nest[j]; 此时代码的核心跟求nest的数组的代码的核心是相同的。KMP算法就不用跟字符串暴力匹配一样,让主串进行回溯,而是一直往前。
我们可以通过一个例子就理解了:
在这里我们可以发现一个规律:
若当前发生不匹配状态时的下标的nest 值 为 x,我们就让子串后移 x + 1 个位置。 如果 nest 值 为 -1,此时主串从data[i+1]开始,子串从data[0]开始(即子串又从首元素开始进行匹配了)。
因为我们普遍都是从下标0开始,下标1的演示就不给出了,仍是一样的思路。
//KMP匹配
void kmpMatch(String* master, String * sub,int * nest)
{
int i = 1;
int j = 1;
while (i < master->length && j < sub->length)
{
if ( j == 0 ||master->data[i] == sub->data[j])
{
i++;
j++;
}
else
{
j = nest[j];
}
}
if (j == sub->length)
{
printf("KMP match success.\n");
}
else
{
printf("KMP match fail.\n");
}
}
void printNext(int* nest, int len)
{
for (int i = 1; i <= len; i++)
{
printf(i == 1 ? "%d" : "->%d", nest[i]);
}
printf("\n");
}
void printNext(int* nest,int len)
{
for (int i = 0; i < len; i++)
{
printf(i == 0 ? "%d" : "->%d",nest[i]);
}
printf("\n");
}
void printString(String* s)
{
for (int i = 0; i length; i++)
{
printf(i == 0 ? "%c" : "->%c", s->data[i]);
}
printf("\n");
}
#include
#include
typedef struct String
{
char* data;
int length;
}String;
//空表
String* initString()
{
String* s = (String*)malloc(sizeof(String));
s->data = NULL;
s->length = 0;
return s;
}
//给串分值
void assignString(String * s,char * arr)
{
if (s->data)
{
free(s);
}
else
{
int len = 0;
char* temp = arr;
while (*temp)
{
len++;
temp++;
}
if(len == 0)
{
s->data = 0;
s->length = 0;
}
else
{
temp = arr;
s->length = len;
s->data = (char*)malloc(sizeof(char) * (len + 1));// \0
for (int i = 0; i <= len; i++, temp++)
{
s->data[i] = *temp;
}
}
}
}
// 公共前后缀 求 nest数组
int * GetNest(String* s)
{
int* nest = (int*)malloc(sizeof(int) * s->length);
int i = 0;
int j = -1;
nest[i] = j;
while (i <= s->length - 1)
{ //求哪一个 就看 前一个
if (j == -1 || s->data[i] == s->data[j]) //如果前一个 和 它当前的nest数组为下标的值相等
{
i++;
j++;
nest[i] = j; // 当前nest的值 = 前一个nest值 + 1
}
else // 不相等,i 不变 变 j 继续比较 直到 相等 或者 j == -1
{
j = nest[j];
}
}
return nest;
}
//kmp匹配
void kmpMatch(String* master, String * sub,int * nest)
{
int i = 0;
int j = 0;
while (i < master->length && j < sub->length)
{
if ( j == -1 ||master->data[i] == sub->data[j])
{
i++;
j++;
}
else
{
j = nest[j];
}
}
if (j == sub->length)
{
printf("KMP match success.\n");
}
else
{
printf("KMP match fail.\n");
}
}
//遍历nest数组
void printNext(int* nest,int len)
{
for (int i = 0; i < len; i++)
{
printf(i == 0 ? "%d" : "->%d",nest[i]);
}
printf("\n");
}
//遍历字符串
void printString(String* s)
{
for (int i = 0; i length; i++)
{
printf(i == 0 ? "%c" : "->%c", s->data[i]);
}
printf("\n");
}
int main()
{
String* s1 = initString();
String* s2 = initString();
assignString(s1, "ABACCABABD");
assignString(s2, "ABAB");
printString(s1);
printString(s2);
GetNest(s2);
int* nest = GetNest(s2);
printNest(next, 4);
kmpMatch(s1,s2,nest);
return 0;
}
#include
#include
typedef struct String
{
char* data;
int length;
}String;
//空表
String* initString()
{
String* s = (String*)malloc(sizeof(String));
s->data = NULL;
s->length = 0;
return s;
}
//给串分值
void assignString(String* s, char* arr)
{
if (s->data)
{
free(s);
}
else
{
int len = 0;
char* temp = arr;
while (*temp)
{
len++;
temp++;
}
if (len == 0)
{
s->data = 0;
s->length = 0;
}
else
{
temp = arr;
s->length = len;
s->data = (char*)malloc(sizeof(char) * (len + 1));// \0
for (int i = 0; i <= len; i++, temp++)
{
s->data[i] = *temp;
}
}
}
}
// 公共前后缀 求 nest数组
int* GetNest(String* s)
{
int* nest = (int*)malloc(sizeof(int) * s->length);
int i = 1;
int j = 0;
nest[i] = j;//nest[1] = 0; 先将公共前后缀不存在的首元素表示出来
while (i <= s->length)
{ //求哪一个 就看 前一个
if (j == 0 || s->data[i] == s->data[j]) //如果前一个 和 它当前的nest数组为下标的值相等
{
i++;
j++;
nest[i] = j; // 当前nest的值 = 前一个nest值 + 1
}
else // 不相等,i 不变 变 j 继续比较 直到 相等 或者 j == -1
{
j = nest[j];
}
}
return nest;
}
//KMP匹配
void kmpMatch(String* master, String* sub, int* nest)
{
int i = 1;
int j = 1;
while (i < master->length && j < sub->length)
{
if (j == 0 || master->data[i] == sub->data[j])
{
i++;
j++;
}
else
{
j = nest[j];
}
}
if (j == sub->length)
{
printf("KMP match success.\n");
}
else
{
printf("KMP match fail.\n");
}
}
//遍历nest数组
void printNext(int* nest, int len)
{
for (int i = 1; i <= len; i++)
{
printf(i == 1 ? "%d" : "->%d", nest[i]);
}
printf("\n");
}
//遍历字符串
void printString(String* s)
{
for (int i = 0; i < s->length; i++)
{
printf(i == 0 ? "%c" : "->%c", s->data[i]);
}
printf("\n");
}
int main()
{
String* s1 = initString();
String* s2 = initString();
assignString(s1, "ABACCABABD");
assignString(s2, "ABAB");
printString(s1);
printString(s2);
GetNest(s2);
int* nest = GetNest(s2);
printNext(nest, 4);
kmpMatch(s1, s2, nest);
return 0;
}
KMP算法其实还可以继续改进。如:主串s=“aaabaaaab” 子串t=“aaaa” 发生不匹配时,按照我们现在的做法,要发生4次不匹配的情况,才能最终匹配成功,要是2次到位的话,岂不是快多了,所以又对nest数组进行了改进,引出了nestvalue数组。nestvalue数组的求法和代码实现,我将在下一篇文章继续进行详细介绍。
KMP算法是一种高效的模式匹配算法,它牺牲了一定的空间去保存nest数组,提高了匹配效率。KMP算法还能更加智能的移动字符串,让字符串达到匹配的状态。KMP算法的核心是减少主串指针的移动,主串指针没有回溯,并且快速达到了匹配状态。其算法的时间复杂度为O(n+m),其中n为文本串的长度,m为模式串的长度,可以看出其比暴力匹配算法的O(nm)要更加高效。KMP算法在字符串匹配、数据压缩等领域有着广泛的应用。
制作不易,真心想让你懂,还是有不足的地方,望见谅嘞,鼓励是最大的支持,希望大家如果又更好的思路和想法也可以一起分享。