20240708 add:
B站视频动画:最浅显易懂的 KMP 算法讲解_哔哩哔哩_bilibili
next数组的作用
记录了模式串和主串(文本串)不匹配时,模式串应该从哪里开始匹配(应该跳过的个数)。
如下图,当在C不匹配,使用最后一个匹配的字符所对应的next值作为下一轮模式串开始比对的位置。
next数组是什么
记录下标i之前(包括i)的字符串中,有多大长度的相同前缀后缀
为是什么next数组有这个功能?
当发生不匹配时,最后一个匹配的字符所对应的next值就是前缀、后缀中最长的相同部分,这部分已经相同,不用再比较了。
整体流程:
1.计算next数组
f
以下为原文:
____________________________________________________________________________
KMP匹配:
定义:KMP是一种改进的字符串匹配算法,其关键是利用匹配失败后的信息,尽量减少模式串和主串的匹配次数以达到快速匹配的目的。
1.KMP做到主串的位置指针i不回退,不像朴素匹配那样在发生不匹配时需要回退主串的位置指针,仅移动模式串的位置指针即可完成匹配。
相关概念:
前缀:不包含最后一个字符的所有以第一个字符开头的连续子串(从左向右扫描),比如字符串abaca,其前缀有:a,ab,aba,abac
后缀:不包含第一个字符的的所有以最后一个字符结尾的连续子串(从左到右扫描),比如字符串abaca,其后缀有:baca,aca,ca,a
最长公共前后缀:就是前缀和后缀中最长的公共部分。上述中就是a。
2.KMP匹配流程
2-1: 开始比较,当模式串的第x个位置的字符和主串第x个位置的字符不相等时,寻找模式串x位置(不包含x)左边的最长公共前后缀,将模式串从前缀位置移动到后缀位置。
2-2: 继续移动主串位置指针,重复2-1。
3.next数组相关
next数组的作用是当发生不匹配时,知道下一步该怎么做。即下一步数组。
next数组大小和模式串大小相等,当模式串某个位置发生不匹配时,就取当前位置对应的next数组的对应值,在对应值的位置继续比较主串和模式串。
next数组表示的是模式串发生不匹配时,不匹配位置左边串(不包含当前位置)的最大公共前后缀
比如模式串为:ABCABCDE 对应的next数组值为:-1,0,0,0,1,2,3,0如何求next数组:当知道next[k]的值,如何求next[k+1]的值?(前提条件:模式串的位置指针为p)
- next[0]=-1,next[1]=0,next[2]开始递归求解。
- 若p[k+1] == p[next[k]+1],则next[k+1] = next[k]+1
- 若p[k+1] != p[next[k]+1],则继续判断若p[k+1] == p[next[next[k]]+1]
学习过程中的一些tips:
1.next数组一定是递增的吗?
不一定,比如模式串是ABCABED,那么字符‘E’处的next值为2(最长公共前后缀为AB),但是字符‘D’处的next值为0。2.KMP匹配的难点是next数组的求法及为什么当主串与模式串不匹配时,可以用next数组的方式移动模式窜的位置指针(不回退主串的位置指针)。
参考博文:https://blog.csdn.net/v_july_v/article/details/7041827 图文并茂,清晰易懂,非常感谢。
C语言代码如下:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h> //signal()
#include<string.h>
#include<sys/stat.h>
#include<time.h>
#include<stdarg.h>
#if 1
#define INFO_DEBUG "%d lines in "__FILE__", complie time is "__TIME__" "__DATE__" \n",__LINE__
#define ADI_RUN_ERROR 1
#define ADI_NULL_POINTER -1
#define ADI_OK 0a
#define ADI_PRINT printf("[%s][%d]:",__FUNCTION__,__LINE__);printf
#define ADI_ASSERT(c,prt,ret) if(c) {printf(prt);return ret;}
#endif
#define SUCCESS 0
#define NULL_POINTER 1
int strLength(char *str)
{
int length = 0;
while('\0' != str[length])
{
length++;
}
return length;
}
/*串str_main和str_sub都存在,str_sub是非空串。若主串str_main中存在和str_sub相同的子串,则返回它在主串str_main中第pos个字符之后第一次出现的位置,否则返回0
pos从0开始。*/
int Index(char *str_main,char *str_sub,int pos)
{
ADI_ASSERT((NULL == str_main)||(NULL == str_sub),"NULL pointer\n",NULL_POINTER);
int size_main = strLength(str_main);
int size_sub = strLength(str_sub);
int start_compare = pos;
while(start_compare+size_sub<=size_main)
{
if(0 == memcmp(&str_main[start_compare],&str_sub[0],size_sub))
{
return start_compare-pos;
}
start_compare++;
}
return 0;
}
/*实现上述Index不使用memcmp做判断,即朴素的模式匹配*/
int Index_ps(char *str_main,char *str_sub,int pos)
{
ADI_ASSERT((NULL == str_main)||(NULL == str_sub),"NULL pointer\n",NULL_POINTER);
int size_main = strLength(str_main);
int size_sub = strLength(str_sub);
int i=0;
int start_compare = pos;
while(start_compare+size_sub<=size_main)
{
while(str_main[start_compare+i] == str_sub[i])
{
i++;
}
if(i == size_sub)
{
return start_compare-pos;
}
else
{
i=0;
}
start_compare++;
}
return -1;
}
/*求next数组*/
void get_next(char* str, int next[])
{
int size_str = strLength(str);
int pos_str = 0; //模式串的位置,从0开始
/*k=-1是next[0]的初值,
整个循环过程中k为模式串str当前字符str[pos_str]的前一个字符str[pos_str-1]的最长公共前后缀中前缀的后一个位置pos
在递归过程中,若str[k] == str[pos_str],则当前字符str[pos_str]的最长公共前后缀可由上一个字符的最长公共前后缀+1计算
*/
int k = -1;
next[0] = k;
while(pos_str<size_str)
{
/*str[k]是前缀的最后一个字符,str[pos_str]是后缀的最后一个字符,若相等,则最长公共前后缀加1*/
if(-1 == k||str[k] == str[pos_str])
{
pos_str++;
k++;
next[pos_str] = k;
}
else
{
k = next[k];
}
}
for(int i=0;i<size_str;i++)
{
printf("%d ",next[i]);
}
printf("\n");
}
/*返回值为主串和模式串匹配成功的在主串中的起始位置
若返回-1,说明匹配失败*/
int Index_kmp(char *str_main,char *str_sub)
{
ADI_ASSERT((NULL == str_main)||(NULL == str_sub),"NULL pointer\n",NULL_POINTER);
int size_main = strLength(str_main);
int size_sub = strLength(str_sub);
int pos_main = 0,pos_sub = 0;
int next[size_sub];
get_next(str_sub,next);
while(pos_main<size_main && pos_sub<size_sub)
{
//匹配成功或-1 == pos_sub(意味着模式串的当前位置没有公共前后缀)
if(str_main[pos_main] == str_sub[pos_sub] || -1 == pos_sub)
{
pos_main++;
pos_sub++;
}
else
{
pos_sub=next[pos_sub];
}
}
if(pos_sub == size_sub)
{
return pos_main-size_sub;
}
else
{
return -1;
}
}
int main()
{
int pos = Index_kmp("ABABCABCABCDE","ABCABCD");
if(-1 != pos)
{
ADI_PRINT("KMP success! pos = %d\n",pos);
}
else
{
ADI_PRINT("can not find !\n");
}
return SUCCESS;
}