KMP算法简述

讨论了一下午绕晕了都没搞懂了算法,看了视频12分钟基本搞懂了

附上B站视频链接:https://www.bilibili.com/video/av3246487?from=search&seid=5216993177757720410

先从暴力搜索开始说起,暴力搜索的匹配模式如下:

初始状态:

 

 

  • 如果当前字符匹配成功(即S[i] == P[j]),则i++,j++,继续匹配下一个字符;

 

  • 如果失配(即S[i]! = P[j]),令i = i - (j - 1),j = 0。相当于每次匹配失败时,i 回溯,j 被置为0。

 

  • 而S[1]肯定跟P[0]失配。为什么呢?因为在之前第1步匹配中,我们已经得知S[1] = P[1] = b,而P[0] = a,即P[1] != P[0],故S[1]必定不等于P[0],所以回溯过去必然会导致失配。暴力匹配的时间复杂度为O(m*n),就像刚才所说,暴力匹配在回溯的过程中执行了很多不必要的步骤,导致时间复杂度加大,那用什么算法可以减少暴力匹配中不必要的回溯呢?

KMP算法:

KMP算法的与暴力匹配的最大区别点就是:当到达不匹配的位置时i是不变的,只有模式串自身在回溯,那具体模式串的j要回溯到什么位置,现在就要引入一个数组next[],当s[i]!=p[j]时,令j=next[j]。而KMP算法最核心的部分就是next数组的求法,有很多种不同的找法,其实思想也差不多,只是每个人的理解不同。

接下来着重介绍next[]数组是如何得到的:

观察发现,j回溯的位置与模式串自身的结构有很大的关系,此处介绍前缀和后缀的概念:

  • 如果给定的模式串是:“abcaby”,从左至右遍历整个模式串,其各个子串的前缀后缀分别如下表格所示:

 

字符串

前缀

后缀

最大公共部分

最大公共部分长度

a

0

ab

a

b

0

abc

a,ab

c,bc

0

abca

a,ab,abc

a,ca,bca

a

1

abcab

a,ab,abc,abca

b,ab,cab,bcab

ab

2

abcaby

a,ab,abc,abca,abcab

y,by,aby,caby,bcaby

0

  •  得到最大长度表:(用数组n[]表示,与next[]数组密切相关)

    下标

    0

    1

    2

    3

    4

    5

    下标对应字符

    a

    b

    c

    a

    b

    c

    n[i]

    0

    0

    0

    1

    2

    0

我们结合最大长度表的意义可以发现,当匹配过程中出现s[i]!=p[j]时i,本应该执行j=0,但公共长度=前缀和后缀相等部分的长度,既然后缀已经匹配了,那前缀也一定匹配,因此直接跳过公共部分,所以应该从字符串头开始跳过j之前字符串的最大公共长度(即p[j-1]) 个字符,即:j=n[j-1];

所以,观察可以发现,next[]数组起始其实就是n[]数组依次往右移一位所构成的,由于next[0]没有元素,因此我们把next[0]初始化为-1,next数组就已经完成构建了。

 

n[]数组构建步骤:

 

 

 

 

下面附上得到next[]数组的代码:

 

 1 int GetNext(string p, int plen)//匹配串自己和自己匹配的过程 
 2 {
 3     int n[1000];
 4     int i = 0, j = 1;//i在首位上 
 5     n[0] = 0;//n[0]初始化为0 
 6     next[c++]=-1;
 7     while (j < plen)//循环条件为模式串p遍历结束 
 8     {
 9         //从头开始比较,寻找到公共部分 
10         if (p[i] == p[j]) {//若发现当前字符也是公共前缀
11             n[j] = i + 1;//在之前已找到公共前缀的下标加1(记录当前已知公共部分的长度)  
12             i++; j++;//若相同,i,j同时移动 
13         }
14         else {
15             if (i == 0)n[j] = 0; 
16             else i = n[i - 1];//若遇到与前缀的最后一个定位符不匹配的字符,应该回溯,去找上一个最大前缀的最大公共部分
17                               //不断前回溯,直到匹配或者i回到第一个字符 
18         }
19         next[c++]=n[i];
20     }
21     return 0;
22 }

 

KMP匹配过程:

 

 

 

KMP算法中循环结束的标志是j<plen

 1 int KMPsearch(string t, string p)
 2 {
 3     int plen = p.length(), tlen = t.length();
 4     GetNext(p, plen);
 5     int i=0, j=0;
 6     while (i < tlen&&j < plen)
 7     {
 8         if (t[i] == p[j])//匹配,同时移动i,j 
 9         {
10             i++; j++;
11         }
12         else //不匹配,移动j,i不动 
13         {
14             if (j == 0)i++;
15             else j = next[j];
16         }
17     }
18     if (j >= plen)return i - j;
19     else return -1;
20 }

 完整代码:

 1 #include<iostream>
 2 #include<cstring>
 3 using namespace std;
 4 int next[1000],c=0;
 5 int GetNext(string p, int plen)//匹配串自己和自己匹配的过程 
 6 {
 7     int n[1000];
 8     int i = 0, j = 1;//i在首位上 
 9     n[0] = 0;//n[0]初始化为0 
10     next[c++]=-1;
11     while (j < plen)//循环条件为模式串p遍历结束 
12     {
13         //从头开始比较,寻找到公共部分 
14         if (p[i] == p[j]) {//若发现当前字符也是公共前缀
15             n[j] = i + 1;//在之前已找到公共前缀的下标加1(记录当前已知公共部分的长度)  
16             i++; j++;//若相同,i,j同时移动 
17         }
18         else {
19             if (i == 0)n[j] = 0; 
20             else i = n[i - 1];//若遇到与前缀的最后一个定位符不匹配的字符,应该回溯,去找上一个最大前缀的最大公共部分
21                               //不断前回溯,直到匹配或者i回到第一个字符 
22         }
23         next[c++]=n[i];
24     }
25     return 0;
26 }
27 
28 int KMPsearch(string t, string p)
29 {
30     int plen = p.length(), tlen = t.length();
31     GetNext(p, plen);
32     int i=0, j=0;
33     while (i < tlen&&j < plen)
34     {
35         if (t[i] == p[j])//匹配,同时移动i,j 
36         {
37             i++; j++;
38         }
39         else //不匹配,移动j,i不动 
40         {
41             if (j == 0)i++;
42             else j = next[j];
43         }
44     }
45     if (j >= plen)return i - j;
46     else return -1;
47 }
48 int main()
49 {
50     string t, p;
51     t = "abcdefgggghhhjjj";
52     p = "jjj";
53     cout << KMPsearch(t, p);
54     return 0;
55 }

输出结果:

转载于:https://www.cnblogs.com/SUHANG12138/p/11168745.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值