[算法入门笔记] 10. KMP


[题目]
给定两个字符串 strmatch,长度为 N N N M M M。如果字符串 str中含有子串 match,返回 matchstr中开始位置

[要求]
如果match的长度大于str长度( M > N M>N M>N),str必然不会含有match,可直接返回-1。但如果 N ≥ M N≥M NM,要求算法复杂度为 O ( M ) O(M) O(M)

1. next数组

1.1 next数组含义

next[i]的含义是在match[i]之前的字符串match[0..i-1]中,必须以match[i-1]结尾的后缀子串与必须以match[0]开头的前缀子串最大匹配长度

  • 后缀字符不能包含match[0],即整个后缀不能是本身
  • 前缀字符不能包含match[i-1],即整个前缀不能是本身

1.2 求解next数组

match[0] :他之前没有字符,next[0]规定为 − 1 -1 1

match[1]next数组定义要求任何子串后缀不能包含第一个字符,故match[1]之前的字符串只有长度为 0 0 0的后缀字符串,next[1]规定为 0 0 0

match[i]

  1. 从左至右依次求解next,求解next[i]时,next[i-1]已经求出
    在这里插入图片描述
    如图I区域是最长匹配前缀,k区域是最长匹配后缀

  2. 如果C和B相等,A之前最长公共前后缀就可以确定,前缀子串为I区域+C,后缀子串为k区域+B,即next[i]=next[i-1]+1

  3. 如果C和B不相等,就要看C之前的前缀和后缀匹配情况
    在这里插入图片描述
    假设字符C是第cn个字符,那么next[cn]就是其最长前缀和最长后缀的匹配长度
    mn区域是相等的,m'区域为k区域最右的区域且长度与m区域相等,字符D是n区域后面一个字符,所以
    接下来比较字符D和字符B是否相等

    • 如果B、D相等,A字符之前的最长前缀与后缀匹配区域就可以确定,前缀区域为n区域+D,后缀区域是m'区域+B,则令next[i]=next[cn]+1
    • 如果B、D不等,继续往前跳到D字符,每一步都有新的字符和B比较,只要相等的情况,next[i]就确定
      在这里插入图片描述
  4. 如果跳到最左位置(match[0]位置),此时next[0]=-1,说明字符A之前的字符串不存在前缀和后缀的匹配情况,则next[i]=0

2. KMP

在这里插入图片描述
假设从str[i]字符出发时,匹配到j位置的字符发现与match中的字符不一致,
现在有match字符串的next数组,next[j-i]表示match[0..j-i-1]这段字符前缀与后缀的最长匹配
在这里插入图片描述
下一次匹配检查让str[i]match[k]进行匹配检查,对于match[]来说,相当于向右滑动,让match[k]滑动到与str[j]在同一个位置上,然后进行后续的匹配检查,一直进行这样的滑动匹配,直到在str某一位置把match完全匹配,说明str中有match,如果滑动到最后也没匹配出来,说明str中没有match
在这里插入图片描述

为什么中间不要检查,必然匹配不了呢

在这里插入图片描述
str[j]位置匹配失败,b区域与c区域相等,a区域与b区域相等,必然a区域与c区域相等
中间的区域不需要检查,直接将a区域滑动到与c区域对齐即可
在这里插入图片描述
假设d区域开始字符是不要检查区域的一个位置,如果这位置开始匹配出match,整个d区域要和从match[0]开始的e区域匹配,de的长度一样,d区域比c区域大,e区域比a区域大
d'区域和d区域一样大,e区域此时是最大前缀,d’区域此时是最大后缀,这与此时next[j-i]的值(ab长度)矛盾,所以必然不相等

3. 时间复杂度分析

str匹配位置是不退回的,match一直向右移动,如果在str某个位置完全匹配出match,整个过程停止,否则match滑动到str最右侧停止,滑动最大长度为 N N N,所以时间复杂度为 O ( N ) O(N) O(N)

4. 算法实现

4.1 getNext

public int[] getNext(char[] match) {
    if (match.length == 1) {
        return new int[]{-1};
    }
    int[] next = new int[match.length];
    next[0] = -1;
    next[1] = 0;
    int i = 2;
    int j = 0;

    while (i < next.length) {
        if (match[i - 1] == match[j]) { // 相等,匹配下一个
            next[i++] = ++j;
        } else if (j > 0) { // 不相等往前跳,继续匹配
            j = next[j];
        } else { // 来到最左边情况,说明i之前字符不存在前后缀匹配情况,匹配下一个
            next[i++] = 0;
        }
    }
    return next;
}

4.2 KMP

public int KMP(String str, String match) {
    if (str == null || match == null ||
            match.length() < 1 || str.length() < match.length()) {
        return -1;
    }
    char[] mainStr = str.toCharArray();
    char[] subStr = match.toCharArray();
    int i = 0;
    int j = 0;
    int[] next = getNext(subStr);
    while (i < mainStr.length && j < subStr.length) {
        if (mainStr[i] == subStr[j]) {
            i++;
            j++;
        } else if (next[j] == -1) { // 往前跳到开头都匹配不出来,找主串下一个位置开始匹配
            i++;
        } else { // 当前匹配不出来且没有跳到子串开头,往前跳
            j = next[j];
        }
    }
    return j == subStr.length ? i - j : -1;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Cyan Chau

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

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

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

打赏作者

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

抵扣说明:

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

余额充值