KMP算法:消除了BF算法的主串指针在相当多个字符比较相等后,只要有一个字符比较不相等便需要回退的缺点。
KMP算法的主要思想:设s为主串,t为模式串,j为s串当前比较字符的下标,k为t串当前比较的字符的下标,令i和j 的初值为0。
1,当 s(j)==t(k)时,j和k分别增1之后再比较。
2,当不相等时,j不变,k改变为next[k]之后再比较。
next数组:存储的是子串匹配时的一个回溯的位置。
0 1 2 3 4 5 6 7 8 9 10 11 12 13
例如: a b a b c a b c d a b c d e j
next数组 -1 0 0 1 2 0 1 2 0 0 1 2 0 0
next数组值的确定规则:在子串中存在两个真子串相等,并且一个以0位置开始,一个以(j-1)位置结束
next[0]=-1 next[1]=0 这两个是固定的,不变的
next[2]:看串“ab”,根据next数组值确定规则,可以确定不存在两个相等的子串,所以next[2]=0
next[3]:看串“aba”,根据next数组值确定规则,可以确定有相同的串“a”,所以next[3]=1
next[4]:看串“abab”,根据next数组值确定规则,可以确定有相同的串“ab”,next[4]=2
next[5]:看串“ababc“,根据next数组值确定规则,可以确定没有相同的串,next[5]=0
。。。。。。。。
按照这个规则,可以写出next数组
得到next数组的代码:
void GetNext(char *sub,int next[])
{
//求子串的next数组
int j=0,k=-1;
int lenth=strlen(sub);
next[0]=-1;
next[1]=0;
while(j<lenth)
{
if(sub[j]==sub[k] || k==-1)
{
next[++j]=++k;
}
else
{
k=next[k];
}
}
}
一脸懵逼,是不是。。。上述代码就是用来求解模式串中每个位置的next[]
值。
下面具体分析,我把代码分为两部分来讲:
(1):i和j的作用是什么?
i和j就像是两个”指针“,一前一后,通过移动它们来找到最长的相同真前后缀。
(2):if...else...语句里做了什么?
假设i和j的位置如上图,由next[j] = k
得,也就是对于位置i来说,区段[0, j - 1]的最长相同真前后缀分别是[0, k - 1]和[j - k, j - 1],即这两区段内容相同。
按照算法流程,if (P[j] == P[k])
,则next[++j] = ++k;
;若不等,则k = next[k]
,见下图:
next[k]
代表[0, k - 1]区段中最长相同真前后缀的长度。如图,用左侧两个椭圆来表示这个最长相同真前后缀,即这两个椭圆代表的区段内容相同;同理,右侧也有相同的两个椭圆。所以else语句就是利用第一个椭圆和第四个椭圆内容相同来加快得到[0, j- 1]区段的相同真前后缀的长度。
细心的朋友会问if语句中k== -1
存在的意义是何?第一,程序刚运行时,k是被初始为-1,直接进行P[j] == P[k]
判断无疑会边界溢出;第二,else语句中k= next[k]
,k是不断后退的,若k在后退中被赋值为-1(也就是k = next[0]
),在P[j] == P[k]
判断也会边界溢出。综上两点,其意义就是为了特殊边界判断。
完整代码:
#include<stdio.h>
#include<string.h>
void GetNext(char *sub,int next[])
{
//求子串的next数组
int j=0,k=-1;
int lenth=strlen(sub);
next[0]=-1;
next[1]=0;
while(j<lenth)
{
if(sub[j]==sub[k] || k==-1)
{
next[++j]=++k;
}
else
{
k=next[k];//如果
}
}
}
int KMP(char *str,char *sub,int next[])
{
int i=0;
int j=0;
int stlen=strlen(str);
int sublen=strlen(sub);
GetNext(sub,next);
while(i<stlen && j<sublen)
{
if(str[i]==sub[j] || j==-1)
{
i++;
j++;
}
else
{
j=next[j];
}
}
if(j==sublen)
{
return i-j;
}
return -1;
}
void main()
{
char str[]="abcababcabc";
char sub[]="abcabc";
int next[10]={0};
int index=KMP(str,sub,next);
for (int i = 0; i < strlen(sub); ++i)
{
printf("%d ",next[i]);
}
printf("\n");
printf("%d\n",index);
}