kmp算法

什么是kmp算法?

kmp算法适用于解决模式匹配问题的一个优化的算法,一般是解决字符串匹配的问题。解决字符串匹配问题还有一种算法,那就是BF算法,也就是暴力破解法,但是BF算法比较耗时间,每一次不匹配时都,母串要回溯到开始匹配时的下一个位置,字串要回溯到开始位置,最坏的时间复杂度是O(n·m)。但是,有一些字符串是有一定规律的,就比如下面这个字符串一样:
image
当在f这个字符不匹配时,我们会发现,我们可以直接将字串的指针直接移到d的位置,为什么呢?因为f的前面有abc,而刚好d前面也有abc,这表明,在母串上面,已经有一串abc和字串匹配过了,那么,我子串就没有必要在把前面那一截abc在比较一次了。这样子就减少了比较的次数了,从而达到优化的效果。kmp算法就是实现,当当前字符不匹配时,我的指针直接跳到像上面f->d的效果,从而实现优化。


所以,KMP算法适用于解决那些前后都有相同字段的字符串的匹配问题。
按照上面的思路,字符串T[0] ~ T[k-1]=T[j-k-1] ~ T[j-1],这时,我们把T[0] ~ T[k-1]叫作j的前缀,T[j-k-1] ~ T[j-1]叫作j的后缀。也就是说,kmp算法适用于有前缀和后缀的字符串的。

如果T[j]和母串不匹配时,我们要执行的操作是:j=k,也就是j的前缀的下一位,现在,我们做一个这样的数组next,就用来存放当T[j]不匹配时j要走的下一个位置。
然后,我们的问题就转化为了怎么求next数组,也就是说,如果在T[0] ~ T[j-1]的字符中,有T[0] ~ T[k-1]=T[j-k-1] ~ T[j-1]的,那我们就把k存放到next[j]中,这样子当j不匹配时,我们就可以直接跳到k的位置了。那么现在问题就转化为求next数组了。

求next数组

我们用两个指针k和j对模式串进行遍历,如果,k和j所指的内容相等,我们就把指针都前移,那么这时候,k的大小(+1之后才是)就表示着当前j指针前的字符串的前缀的长度,也就是当j不匹配时,j将要走的位置。上面这句话在代码中实现如下:

if(sub[k]==sub[j])
{
	j++,k++;
	Next[j]=k;
}

那如果k和j所指的的容不相等呢?此时分为以下两种情况:

  1. k=0,此时,j只能往前走
  2. k≠0,此时,k已经往前走了,但是现在不匹配了,也就是说,当前j的前缀已经达到最大,不会再增大了,对于往后的字符,就会有新的前后缀了。那么此时k是不是要从头开始呢,如果k前的字符串没有前后缀的话,确实是这样的。如果有的话,事实上我们把k移到Next[k]就好了。这个过程和子串和母串匹配的过程是一样的。下面给个例子:
    image
    k和j不匹配了,那么j的前缀长度(即k的大小+1)又要从小变大了,也就是k要回退。这时,发现k的前缀:agc 和j前面的agc相同,那么我们把k移到T[3]的位置,即相当于前面的acg已经匹配了。在看,上述情况中,Next[k]=3,这不就巧了吗!事实上这不是巧合,这个就是像子串和母串不匹配时的情况是一样的。下面给出以上两种情况的代码:
if(k>0&&sub[k]!=sub[j]
{
	k=Next[k];
}
else if(k==0)
{
	j++;
}

所以,Next数组就算求完了。给出完整代码:

int Next[100] = {0};
void getNext(string sub)
{
    int k = 0;
    int j = 1;
    int slen = sub.length();
    while (j < slen)
    {
        if (sub[k] == sub[j])
        {
            j++, k++;
            Next[j] = k;
        }
        else if (k > 0 && sub[k] != sub[j])
        {
            k = Next[k];
        }
        else if (k == 0)
        {
            j++;
        }
    }
}

Next数组优化

事实上,上面的的情况时可以优化的。
我们把k=0和k>0时匹配的情况整合起来可以这么做:我们把k的起始值设为-1,然后把Next[0]=-1;先上代码:

int Next[100] = {0};
void getNext(string str) {
    int k = -1;
    int j = 0;
    Next[0] = -1;
    int len = str.length();
    while (j < len) {
        if (k == -1 || str[k] == str[j]) {
            k++;
            j++;
            Next[j] = k;
        } else {
            k = Next[k];
        }
    }
}

为什么可以这样?
我们可以发现,k=0时不匹配以及k!=0时匹配,j都要向前进的。
我们把k=-1,Next[0]=-1。刚开始是,k=-1,两个往前走,k=0;j=1;
这样子我们就把k=0的情况整合到k,j匹配的情况一同进行了,从而简化代码。

kmp算法实现

kmp算法最重要的就是求Next数组了,Next数组搞定了,kmp也就实现了,这里直接给代码了。

#include <bits/stdc++.h>
using namespace std;

int Next[100] = {0};
void getNext(string str) {
    int k = -1;
    int j = 0;
    Next[0] = -1;
    int len = str.length();
    while (j < len) {
        if (k == -1 || str[k] == str[j]) {
            k++;
            j++;
            Next[j] = k;
        } else {
            k = Next[k];
        }
    }
}
int kmp(string momstr, string substr) {
    int mlen = momstr.length();
    int slen = substr.length();
    int i = 0, j = 0;
    getNext(substr);
    while (i < mlen && j < slen) {
        if (j == -1 || momstr[i] == substr[j]) {
            i++, j++;
        } else {
            j = Next[j];
        }
    }
    if (j == slen)
        return i - j + 1;
    else
        return -1;
}
int main() {
    string momstr, substr;
    cin >> momstr >> substr;
    cout << kmp(momstr, substr);
    return 0;
}

到此为止,kmp算法就算讲完了。以上内容仅仅是个人的理解,若有什么不妥当的地方恳请大佬指正!

参考资料

详解kmp算法
印度小哥的KMP算法讲解(字幕版)-转自Youtube

posted @ 2021-10-12 09:22  ycloong  阅读(405)  评论(1)    收藏  举报