KMP的next数组求法详解

转自:https://blog.csdn.net/yutong5818/article/details/81319120

kmp算法的精髓就在于next数组,从而达到跳跃式匹配的高效模式。

而next数组的值是代表着字符串的前缀与后缀相同的最大长度,(不能包括自身)。

"前缀"指除了最后一个字符以外,一个字符串的全部头部组合;

"后缀"指除了第一个字符以外,一个字符串的全部尾部组合。

这里举个例子:

 

 

这样我们就求出来了next数组

模式串t

A

B

A

B

A

A

下标

0

1

2

3

4

5

next

0

0

1

2

3

1

当前后缀特别长的时候(假设该串大小为1000),我们当然不可能挨个去比较前后缀,next数组怎样用代码去求呢?

仍然是上面那个例子

A B A B A A

模式串t

A

B

A

B

A

A

下标

0

1

2

3

4

5

next

此时next数组的值,全为未知,

1.初始化,next[0]为0;

因为next[0]代表t[0]~t[0]即"X","X"的前缀和后缀都为空集,共有元素的长度为0.

所以无论X的值为任意值,next[0]=0;

模式串t

A

B

A

B

A

A

下标

0

1

2

3

4

5

next

0

2.求解next[1],

  • t[0] != t[1] next[1]为0; 假设此时为XY ,无最大前后缀,故next值为0.
  • t[0]==t[1] next[1]为1, 假设此时为XX,最大前后缀为X,故next值为1.

得出next[1]=0;

模式串t

A

B

A

B

A

A

下标

0

1

2

3

4

5

next

0

0

3.求解next[2],

  • t[0]!=t[2] next[2]为0; 假设此时为XYZ,前缀为 XY,后缀为YZ,无最大前后缀,故next值为0.
  • t[0] == t[2] next[2]为1; 假设此时为XYX,前缀为XY,后缀为YX,最大前后缀为X,故next值为1.

在该题中,的next[2]=1

模式串t

A

B

A

B

A

A

下标

0

1

2

3

4

5

next

0

0

1

4.求解next[3]

在该题中,next[3]代表t[0]~t[3]即"ABAB"的最大前后缀,即"AB",长度为2.

在求next[3]时,如何用代码求

  • t[next(3-1)]=t[3]:next[3] = next[next(3-1)+1]+1;

疑问:为什么求next[3]要把t[3]与t[next(3-1)]做对比,

首先我们要明白next[3]是什么,next数组的值是代表着字符串的前缀与后缀相同的最大长度,(不能包括自身),

所以next[3]代表的是t[0]~t[3],这四个字符放入前缀与后缀相同的最大长度

t[next(3-1)]=t[1],为什么要将t[3]和t[1]对比,也就是t[3]与t[next(2)]对比,这是我之前搞不懂的一个地方!!!!

然后看了一些大佬的图解似乎好像仿佛明白了。

 

模式串t

A

B

A

B

A

A

下标

0

1

2

3

4

5

next

0

0

1

我们求next[3]的时候,已经求出next[0]~next[2],此时我们知道了next[2]=1,意味着next[2]是代表着字符串的前缀与后缀相同的最大长度为1。

也就是对于ABA来说,前缀与后缀相同的最大长度为1,这是我们已知的条件,现在我们要知道对于ABAB来说,前缀与后缀相同的最大长度为??????

对于ABA来说,前缀"AB",后缀"BA",对于ABAB来说,前缀“ABA”,后缀“BAB”,现在我们观察得出结论

ABAB的前缀和后缀必然分别包含ABA的前缀和后缀,即next[3]字符串的前缀和后缀必然分别包含next[2]的前缀和后缀,

无论该字符串是何值。

既然我们已经知道了next[2]=1,代表着ABA的前缀与后缀相同的最大长度为1,那我们计算next[3]的时候,就知道了第一个字符必然是相同的,所以我们无需再比较第一个字符了,(KMP算法就是为了省这一步!!!!),所以我们将t[3]直接与t[1]而不是t[0]比较,若t[3]=t[1],那我们可以直接说next[3]=2=next[1]+1.

  • t[next(3-1)]!=t[3]:怎么办? 看下面,这就是KMP算法的精髓

 
 
  1. void makeNext(char s[],int next[])
  2. {
  3. int len = strlen(s);
  4. next[ 0]= 0; //初始化
  5. for( int i= 1,k= 0;i<len;i++)
  6. {
  7. while(k> 0 && s[k]!=s[i]) //这个while是最关键的部分
  8. k=next[k -1];
  9. //等价于 k=next[next[i-1]-1]
  10. //等号右边的k起的只是下标的作用
  11. if(s[k]==s[i])
  12. k++; //相等就+1
  13. next[i]=k; //赋值
  14. }
  15. }

 

在该题中,t[0]="A",t[3]="B",显然不相等,

所以需要用该算法

如何求next[3]呢,将t[3]与t[next(3-1)]对比

即t[3]与t[next[2]]=t[1]做对比,

此时t[3]="B",t[1]="B",满足t[3]=t[next(3-1)],next[3]=next[t[[next(3-1]+1]+1;

得到t[3]=next[1+1]+1=1+1=2;

模式串t

A

B

A

B

A

A

下标

0

1

2

3

4

5

next

0

0

1

2

PS:这里为了理解可以推广为,若经过KMP算法得到t[3]=t[0],则next[3]=next[0+1]+1,若经过KMP算法得到t[4]=t[2],则next[4]=next[2+1]+1

(可以记忆为---里面加1外面也要加1)

这个时候我们虽然把next[3]计算出来了,但是肯定有人问了,要是t[3]与t[next(3-1)]还是不相等怎么办?????

 

这个时候我们举另外一个例子

A B A B A B A C

0 1 2 3 4 5 6 7

next[6] = 5

即前缀为t[0]~t[4] A B A B A

后缀为t[2]~t[6] A B A B A

 

next[4] = 3

即前缀为t[0]~t[2] A B A

后缀为t[2]~t[4] A B A

 

next[4]的前缀一定是next[6]的前缀

next[4]的后缀也一定是next[6]的后缀

 

现在我们要求next[7],将t[7]与t[next(7-1) ] 比较(此时t[next(7-1)]=t[5]),发现还是不相等

那么可以将t[7]与t[next(next(7-1)-1)]比较 (此时t[next(next(7-1)-1)]=t[3]),如果相等,则next[7] = next[3+1] +1;

 

(同前面所述一样,里面加1,外面也加1)

不相等就重复此过程,直到t[7]与t[0]比较.

好了,关于KMP算法我们已经通过例题全部解释了一遍,此时这个例子还没有算完,我们通过KMP算法得出最终结果来验证是否KMP算法是否可靠

模式串t

A

B

A

B

A

A

下标

0

1

2

3

4

5

next

0

0

1

2

这个时候求next[4]

1.判断t[4]与t[next(4-1)]是否相等 ,t[4]="A",t[next(4-1)]=t[2]="A"

2.next[4]=next[2+1]+1=2+1=3

再求next[5]

1.判断t[5]与t[next(5-1)]是否相等,t[5]="A",t[next(5-1)]=t[3]="B";

2.由于t[5]与t[next(5-1)]不相等,所以要继续循环,

     2.1 判断t[5]与t[next(next(5-1)-1)]是否相等,t[5]="A",t[next(next(5-1)-1)]=t[next(3-1)]="B";

     由于t[5]与t[next(next(5-1)-1)]还是不相等,继续循环

                   2.1.1 判断t[5]与t[next(next(next(5-1)-1))-1]是否相等,t[5]="A",t[next(next(next(5-1)-1))-1]=t[next(1-1)]=t[0]="A";

                   此时满足t[5]与t[next(next(next(5-1)-1))-1]相等,所以next[5]=next[0+1]+1=1(记忆口诀为里面加去,外面也加1)

模式串t

A

B

A

B

A

A

下标

0

1

2

3

4

5

next

0

0

1

2

3

1

这个时候我们就得到了完整的表格

与我们用非代码计算出来的结果对比

可以发现,用KMP算法所得的结果是可靠的。

下面附上kmp完整代码


 
 
  1. #include<iostream>
  2. #include<algorithm>
  3. #include<stdio.h>
  4. #include<string.h>
  5. #include<stdlib.h>
  6. using namespace std;
  7. void makeNext(char s[],int next[])
  8. {
  9. int len = strlen(s);
  10. next[ 0]= 0;
  11. for( int i= 1,k= 0;i<len;i++)
  12. {
  13. while(k> 0 && s[k]!=s[i])
  14. k=next[k -1];
  15. if(s[k]==s[i])
  16. k++;
  17. next[i]=k;
  18. }
  19. }
  20. int kmp(char t[],char s[])
  21. {
  22. int len1 = strlen(t);
  23. int len2 = strlen(s);
  24. int next[len2];
  25. makeNext(s,next);
  26. for( int i= 0,j= 0;i<len1;i++)
  27. {
  28. while(j> 0 && t[i]!=s[j])
  29. {
  30. j=next[j -1];
  31. }
  32. if(t[i]==s[j])
  33. j++;
  34. if(j==len2)
  35. return i-j+ 1;
  36. }
  37. }
  38. int main()
  39. {
  40. char t[]= "1234561123458412";
  41. char s[]= "611";
  42. cout<<t<< endl;
  43. cout<<s<< endl;
  44. cout<< "下标为"<<kmp(t,s)<< endl;
  45. }

 

  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值