简明扼要 KMP 算法

朴素字符串匹配

首先我们看看最普通的字符串匹配方法

可以看出朴素算法的匹配思想是用两个指针分别指向目标串和模式串,一旦发生了不匹配的情况,两个指针都回退,然后从目标串的新位置重新开始匹配,复杂度为 O(mn),对于数据量比较大的字符串匹配情况来说必定超时。于是引出了我们下面的 KMP 算法。

KMP

KMP算法的核心是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是通过一个 next() 函数实现,函数本身包含了模式串的局部匹配信息。KMP算法的复杂度为 O(m+n)。事实上可以理解为,发生失配时,目标串指针不动,只回退模式串指针,即只需要将模式串移动到特定位置然后开始新一轮比较。下面我们引入一些概念。

前缀后缀最大公共元素长度

然后我们将得到的最大公共元素长度右移一位得到 next() 数组,后面我们再介绍这个数组的具体求法,先看看如果已经知道了 next() 数组我们怎么去使用它。

通过 next() 数组进行匹配

发生失配时,模式串向右移动的位数 = 已经匹配的字符数 - 失配字符的上一位字符的前缀后缀的公共元素的最大长度值(即 next() 数组对应的值)。如图:

使用 next() 数组进行匹配的代码如下:

int match(char w[], char t[], int next[]){  //w是模式串,t是目标串
    int cnt = 0;
    int tlen = strlen(t);
    int wlen = strlen(w);
    int p = 0, q = 0;  //p指向模式串,q指向目标串
    while(q < tlen){
        if(t[q] == w[p]){
            ++q;
            ++p;
        }
        else if(p >= 0){
            p = next[p];
        }
        else{
            ++q;
            p = 0;
        }
        if(p == wlen){
            ++cnt;
            p = next[p];
        }
    }
    return cnt;
}

该代码的作用是用来统计模式串在目标串中出现了多少次,根据实际题目的不同,匹配代码应该有相应修改。

下面我们看看如何求出 next() 数组。

求 next() 数组

假设前 k 个字符已经匹配完成,在新的位置上,pk == pj,于是我们可以将 j、k 均移动到下一位,令 next[j] = k 即可。

假设前 k 个字符已经匹配完成,在新的位置上,pk != pj,此时我们应该将 j 指针不动,左移回退 k 指针去找一个以 pj 这个字符结尾的前缀。可以把匹配的过程想象成下面这样:

这实际上可以看作是一个拿自己的前缀当成模式串,后缀当成目标串去进行匹配的过程。和我们拿着 next() 数组去匹配目标串是一样的思想。代码实现如下:

int Next[maxn];
int plen = p.length(), wlen = w.length();
Next[0] = -1;
int k = -1, j = 0;
while(j < plen){
    if(k == -1 || p[j] == p[k]){
         ++j;
         ++k;
         Next[j] = k;
     }
     else k = Next[k];
}

匹配的过程实际上可以看作是 next() 数组推动模式串右移去和目标串进行匹配。

next() 数组的优化

举个例子:abcabd

next() 值为-100012

如果第二个b失配,移动到第一个b还是依然会失配,所以如果next[i]与i的字母相同,那么next[i]可以直接赋值为next[next[i]],只需要在之前的求 next() 数组过程中简单修改一下即可,代码如下:

int Next[maxn];
int plen = p.length(), wlen = w.length();
Next[0] = -1;
int k = -1, j = 0;
while(j < plen){
    if(k == -1 || p[j] == p[k]){
        ++j;
        ++k;
        if(p[j] != p[k]) Next[j] = k;
        else Next[j] = Next[k];
     }
     else k = Next[k];
}

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值