KMP算法总结

最近学习了一下 KMP 算法,写一下总结,免得忘了。

KMP算法:
1.1 暴力匹配的浪费:

假设正在进行下图这样的匹配,
这里写图片描述
暴力匹配一旦匹配失败,模式串就会回退到开头进行匹配;
我们可以看到,失配字符 ‘C’ 的前两个字符 (AB) 和开头 (AB) 是一样的,如果挪到开头去匹配,很明显会进行一些不必要的匹配;
显然,将箭头所指字符与失配处进行匹配才是 比较 优的。
这里写图片描述
KMP 算法就是基于这个思想的。只要知道有前缀与后缀匹配最大长度,那某个位置失配之后,就能迅速的像上面那样跳到一个 比较 优的位置进行匹配。
为什么是 “比较优” ?后面再说。

1.2 什么叫 “前缀和后缀匹配” ?

对于一个位置 i i ,如果 str[0,k1]=str[ik,i1],那么就说 i i 位置前缀匹配后缀的最大长度为 k ;我们用 Next N e x t 数组存起来,即 Next[i]=k N e x t [ i ] = k
那么,对 Next[] N e x t [ ] 下个定义:

Next[i]:str[0,i1] ∙ N e x t [ i ] : s t r [ 0 , i − 1 ] 中前缀匹配后缀的最大长度。

如果我们计算出了模式串 P P 的每个 Next[i] ,那么在 pos p o s 位置处失配时,立马就可以跳到 Next[pos] N e x t [ p o s ] 处进行匹配;
所以, Next N e x t 数组还可以这样理解:

Next[i]: ∙ N e x t [ i ] : i i 位置失配时应该跳往的匹配位置。

比如上面的模式串 P P :

i 0 1 2 3 4 5 6 7
P A B D C A B C ‘\0’
Next -1 0 0 0 0 1 2 0

i=0,对于模式串的首字符,我们统一为 Next[0]=1 N e x t [ 0 ] = − 1
i=1 i = 1 ,前面的字符串为 A A ,其最长相同真前后缀长度为 0,即 Next[1]=0 N e x t [ 1 ] = 0
i=2 i = 2 ,前面的字符串为 AB A B ,其最长相同真前后缀长度为 0 0 ,即 Next[2]=0
i=3 i = 3 ,前面的字符串为 ABD A B D ,其最长相同真前后缀长度为 0 0 ,即 Next[3]=0
i=4 i = 4 ,前面的字符串为 ABDC A B D C ,其最长相同真前后缀长度为 0 0 ,即 Next[4]=0
i=5 i = 5 ,前面的字符串为 ABDCA A B D C A ,其最长相同真前后缀为 A A ,即 Next[5]=1
i=6 i = 6 ,前面的字符串为 ABDCAB A B D C A B ,其最长相同真前后缀为 AB A B ,即 Next[6]=2 N e x t [ 6 ] = 2
i=7 i = 7 ,前面的字符串为 ABDCABC A B D C A B C ,其最长相同真前后缀长度为 0 0 ,即 Next[7]=0

还是开始那个例子,当 i=6 i = 6 时,失配,立马就可以跳到 i=2 i = 2 的位置进行匹配; faster了有没有?

1.3 如何求Next数组?

假设我们现在正在计算 Next[i] N e x t [ i ] ,即之前的 Next[0,,i1] N e x t [ 0 , … , i − 1 ] 已经得到了;
先上代码:

void Get_Next(string P) {
    memset(Next, 0, sizeof(Next));
    int P_len = P.length();
    int i = 0;   //当前枚举的位置
    int p = -1;   //上一次前后缀匹配的最大长度+1,说白了就是Next[i-1]的后一位
    Next[0] = -1;
    while(i < P_len) {
        if(p == -1 || P[i] == P[p]) {
            i++;
            p++;
            Next[i] = p;
        }
        else p = Next[p];
    }
}

代码中最重要的就是那个 if…else… 语句;
一脸懵逼???不怕,上图:
这里写图片描述
假设 i i p 的位置如上图,上一次前后缀匹配的最大长度为 p1 p − 1 ,即绿色的两个椭圆是相等的;
(1) if (P[i] == P[p]) ,则往后走就是了;
p == -1 又是为什么呢?一是 p 初始化为-1,总得往后走吧!二是当前后缀匹配长度为 0 时,p会为-1,这时候也得往后走;
(2) else
这里写图片描述
Next[p] N e x t [ p ] 的定义可知,前两个绿色的椭圆是相等的;于是可知第 1 1 个和第 4 个椭圆是相等的;
因此,令 p=Next[p] p = N e x t [ p ] 来加速匹配;

1.4 完整代码:

这其实是一道题:HDU-2087

/**********************************************
 *Author*        :XzzF
 *Created Time*  : 2018/4/26 7:07:32
 *Ended  Time*  : 2018/4/26 12:56:37
*********************************************/

#include <cstdio>
#include <cstring>
#include <cmath>
#include <cstdlib>
#include <algorithm>
#include <string>
#include <iostream>
using namespace std;
typedef long long LL;
const int inf = 1 << 30;
const LL INF = 1LL << 60;
const int MaxN = 10005;

string S, P;
int Next[MaxN + 5];

void Get_Next(string P) {
    memset(Next, 0, sizeof(Next));
    int P_len = P.length();
    int i = 0;     //当前枚举的位置
    int p = -1;  //上一次前后缀匹配的最大长度+1,说白了就是Next[i-1]的后一位
    Next[0] = -1;
    while(i < P_len) {
        if(p == -1 || P[i] == P[p]) {
            i++;
            p++;
            Next[i] = p;
        }
        else p = Next[p];
    }
}

int KMP(string S, string P) {
    Get_Next(P);
    int i = 0;      //index of S
    int j = 0;      //index of P
    int cnt = 0;
    while(i < S.length() ) {
        if(j == -1 || S[i] == P[j]) {
            i++;
            j++;
        }
        else j = Next[j];
        if(j == P.length()) {
            j = 0;    //不相交匹配则为0,相交匹配则为Next[j]
            cnt++;
        }
    }
    return cnt;
}

int main()
{
    while(cin >> S >> P) {
        if(S[0] == '#') break;
        printf("%d\n", KMP(S, P));
    }
    return 0;
}
KMP优化:

上面还有一个问题没有解决,为什么说 “比较优” ?

2.1 问题所在:

其实一开始那个表格就能看到问题了,但还是看个”严重”点的吧!

i012345678910
PABACDABACE‘\0’
Next-10010012340

假设 i=8 i = 8 时失配,按照之前的KMP算法,就会把 p=3 p = 3 处的字符拿过来匹配;然而,这两个字符是相同的,就增加了一些不必要的匹配;
稍微修改一下代码就能解决这个问题:

void Get_Nextval(string P) {   //KMP优化
    int P_len = P.size();
    int i = 0;   // P 的下标
    int p = -1;  
    Nextval[0] = -1;

    while (i < P_len)
    {
        if (p == -1 || P[i] == P[p])
        {
            i++;
            p++;

            if (P[i] != P[p])
                Nextval[i] = p;
            else
                Nextval[i] = Nextval[p];  //既然相同就继续往前找
        }
        else
            p = Nextval[p];
    }
}
i012345678910
PABACDABACE‘\0’
Next-10010012340
Nextval-10-110-10-1140

优化之后,当 i=8 i = 8 失配时,就能直接跳到 p=1 p = 1 处匹配,faster 了有没有?匹配方式还是没变的;

严格来讲,未优化的 KMP 算法叫 MP算法;但一般情况下,未优化的 KMP 算法已经够用了;

KMP算法(未优化): Next[] N e x t [ ] 表示前后缀匹配的最大长度,还有一些骚操作,后面再说;
KMP算法(优化): Nextval[] N e x t v a l [ ] 表示最优长度,但不一定是最长;快是快了,功能少了;


复杂度 O(n+m) O ( n + m ) (并不会证明)

More about KMP :
KMP求最小循环节:

Picture one:
这里写图片描述

Picture two:
这里写图片描述

Picture three:
这里写图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值