字符串匹配——KMP算法

目录

KMP(Knuth Morris Pratt)

KMP算法复杂度分析

字符串匹配中除了简单的BF(Brute Force)、RK(Rabin-Karp)算法,还有更高效、较难理解的

BM(Boyer-Moore)和KMP(Knuth Morris Pratt)算法。这次来聊聊KMP(Knuth Morris Pratt)算法。

KMP(Knuth Morris Pratt)

KMP算法是根据三位作者(D.E.Knuth,J.H.Morris和V.R.Pratt)的名字来命名的,算法的全称是Knuth Morris Pratt算法,简称为KMP算法。

KMP算法的核心思想和BM(Boyer-Moore)非常相近。(BM算法简介)

为了表述起来方便,好前缀的所有后缀子串中,最长的可匹配前缀子串的那个后缀子串,叫作最长可匹配后缀子串;对应的前缀子串,叫作最长可匹配前缀子串。

需要提前构建一个数组,用来存储模式串中每个前缀(这些前缀都有可能是好前缀)的最长可匹配前缀子串的结尾字符下标,叫做next数组。

数组下标是每个前缀的结尾字符下标,数组值是这个前缀的最长可匹配前缀子串的结尾字符下标。

模式串:a b a b a b e a b

模式串前缀 (好前缀候选情况枚举)模式串前缀 结尾字符下标最长匹配前缀子串最长匹配后缀子串最长匹配前缀子串 结尾字符下标next值
a0无子串无子串-1(表示不存在)netx[0]=-1
ab1ab-1netx[1]=-1
aba2aa0netx[2]=0
abab3abab1netx[3]=1
ababa4abaaba2netx[4]=2
ababab5abababab3netx[5]=3
abababe6无符合子串无符合子串-1netx[6]=-1
abababea7aa0netx[7]=0
abababeab8abab1netx[8]=1

在next数组的帮助下,KMP算法就方便实现了。假设next数组已计算好,KMP的算法框架大致如下:

// a和b表示主串和模式串,n和m分表表示主串和模式串的长度
public static int kmp(char[] a, int n, char[] b, int m) {
    int[] next = getNextArr[b, m];// 通过模式串获取next数组
    int j = 0;
    for(int i = 0; i < n; i++) {
        while(j > 0 && a[i] != b[j]) { // 一直找到a[i] = b[j]
            j= next[j - 1] + 1; 
        }
        if (a[i] == b[j]) { 
            ++j;
        }
        if (j == m) { // 找到匹配的串了
            return i - m + 1;
        }
    }
    return -1;
}

接下来需要解决的是getNextArr了。

我们按照下标从小到大,依次计算next数组的值。当我们要计算next[i]的时候,前面的next[0],next[1],……,next[i-1]应该已经计算出来了。 如果next[i-1]=k-1,也就是说,子串b[0, k-1]是b[0, i-1]的最长可匹配前缀子串。如果子串b[0, k-1]的下一个字符b[k],与b[0, i-1]的下一个字符b[i]匹配,那子串b[0, k]就是b[0, i]的最长可匹配前缀子串。所以,next[i]等于k。但是,如果b[0, k-1]的下一字符b[k]跟b[0, i-1]的下一个字符b[i]不相等呢?这个时候就不能简单地通 过next[i-1]得到next[i]了。

我们假设b[0, i]的最长可匹配后缀子串是b[r, i]。如果我们把最后一个字符去掉,那b[r, i-1]肯定是b[0, i-1]的可匹配后缀子串,但不一定是最长可匹配后缀子串。所 以,既然b[0, i-1]最长可匹配后缀子串对应的模式串的前缀子串的下一个字符并不等于b[i],那么我们就可以考察b[0, i-1]的次长可匹配后缀子串b[x, i-1]对应的可匹配前缀子串b[0, i-1-x]的下一个字符b[i-x]是否等于b[i]。如果等于,那b[x, i]就是b[0, i]的最长可匹配后缀子串。

次长可匹配后缀子串肯定被包含在最长可匹配后缀子串中,而最长可匹配后缀子串又对应最长可匹配前缀子串b[0, y]。于是,查找b[0, i-1]的次长可匹配后缀子串,这个问题就变成,查找b[0, y]的最长匹配后缀子串的问题了。

按照这个思路,我们可以考察完所有的b[0, i-1]的可匹配后缀子串b[y, i-1],直到找到一个可匹配的后缀子串,它对应的前缀子串的下一个字符等于b[i],那这个b[y, i]就是b[0, i]的最长可匹配后缀子串。

// b表示模式串,m表示模式串的长度
private static int[] getNexts(char[] b, int m) {
    int[] next = new int[m];
    next[0] = -1;
    int k = -1;
    for (int i = 1; i < m; ++i) {
        while (k != -1 && b[k + 1] != b[i]) {
            k = next[k];
        }
        if (b[k + 1] == b[i]) {
            ++k;
        }
        next[i] = k;
    }
    return next;
}
KMP算法复杂度分析

KMP算法包含两部分,第一部分是构建next数组,时间复杂度主要取决于while循环里面k=next[k]总的执行次数,这部分时间复杂度是O(m)。第二部分才是借助next数组匹配,取决于while循环中的这条语句总的执行次数也不会超过n,所以这部分的时间复杂度是O(n)。

贴个carl的讲解连供诸君参考
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
KMP算法是一种字符串匹配算法,用于在一个文本串S内查找一个模式串P的出现位置。它的时间复杂度为O(n+m),其中n为文本串的长度,m为模式串的长度。 KMP算法的核心思想是利用已知信息来避免不必要的字符比较。具体来说,它维护一个next数组,其中next[i]表示当第i个字符匹配失败时,下一次匹配应该从模式串的第next[i]个字符开始。 我们可以通过一个简单的例子来理解KMP算法的思想。假设文本串为S="ababababca",模式串为P="abababca",我们想要在S中查找P的出现位置。 首先,我们可以将P的每个前缀和后缀进行比较,得到next数组: | i | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | | --- | - | - | - | - | - | - | - | - | | P | a | b | a | b | a | b | c | a | | next| 0 | 0 | 1 | 2 | 3 | 4 | 0 | 1 | 接下来,我们从S的第一个字符开始匹配P。当S的第七个字符和P的第七个字符匹配失败时,我们可以利用next[6]=4,将P向右移动4个字符,使得P的第五个字符与S的第七个字符对齐。此时,我们可以发现P的前五个字符和S的前五个字符已经匹配成功了。因此,我们可以继续从S的第六个字符开始匹配P。 当S的第十个字符和P的第八个字符匹配失败时,我们可以利用next[7]=1,将P向右移动一个字符,使得P的第一个字符和S的第十个字符对齐。此时,我们可以发现P的前一个字符和S的第十个字符已经匹配成功了。因此,我们可以继续从S的第十一个字符开始匹配P。 最终,我们可以发现P出现在S的第二个位置。 下面是KMP算法的C++代码实现:
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Elaine202391

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值