KMP 算法

KMP算法

简单模式匹配算法

    简单模式匹配算法(俗称,暴力匹配算法)如下动图所示:
在这里插入图片描述
    即S每一个子串都会与模式串P匹配一遍,其最坏时间复杂度为O(nm),n和m分别为主串和模式串的长度。

KMP 算法的核心思想

    假设主串S和模式串P在第三个位置发生不匹配,这个时候我们其实可以确认前面一定是匹配的!!
在这里插入图片描述
    即,如下图所示:
在这里插入图片描述
    如果我们充分利用模式串的特征,下次匹配我们就可以保持主串不动,即只用挪动模式串,如下图所示:
在这里插入图片描述
    从而达到减少匹配次数的效果。
    因此,KMP算法的核心就是在分析我们的模式串,即next数组!

字符串的前缀、后缀和部分匹配值

  • 前缀:除最后一个字符以外,字符串的所有头部子串
  • 后缀:除第一个字符外,字符串的所有尾部子串
  • 部分匹配值:字符串的前缀和后缀的最长相等前后缀长度
    在这里插入图片描述

图解获取 Next 数组代码

Next 数组的定义

对于字符串s的第i个字符s[i],next[i]定义为字符s[i]前面最多有多少个连续的字符和字符串s从初始位置开始的字符匹配。

    即,当主串(i)与模式串(j)发生不匹配时,此时 j = next[j] 继续匹配(详见上面的KMP的核心思想)。

详解 Next 数组与部分匹配值之间的关系

在这里插入图片描述

    如果主串和模式串在 i = 4 的时候,发生的不匹配,那么说明 i = 4之前的模式串是与主串匹配的,即如下图所示:
在这里插入图片描述
    这个时候我们应该观察i = 4之前的子串的特征,而用之前的部分匹配值,我们可以得到’aba’子串的部分匹配值是1
    最后可得next[4] =1+1,即接下来,主串接着与模式串j = 2的位置继续匹配。
    因此,最后的效果如下图所示:
在这里插入图片描述

详解 Next 数组

在这里插入图片描述

注意这里模式串a b a b a对应的下标分别为1 2 3 4 5
而 next 数组中存储的是 “模式串” 回溯时对应的下标

  1. 默认next[1] = 0;
  2. 通过前面对于 Next 数组和部分匹配值的关系,可以得知‘a’的最长相等前后缀长度为0,则next[2] = 0 + 1 = 1(next[2] 的值默认是 1)
  3. 求 next[2]:
    (即,“ababa” 第三个值 “a” 匹配失败时应该 j 应该回溯的下标,因此我们需要考虑的是 “ab” 子串)
    在这里插入图片描述
        已知 next[1] 是在匹配第二个值 “b” 的时发生不匹配需要回溯的下标,而此刻我们在求 next[2] 的时候,是不是默认这个 “b” 匹配成功呢?
        因此,只需要判断 next[1] 回溯时指向的元素和 “b” 是否相同,有以下两种情况:(假设 i 为所求 next 元素前一个元素,令 j = next[i],那么 next[i+1] 就是我们所要去求的值)
    (1)如果 i == j;
    (2)相同,则 next[i+1] = j + 1,结束;
    (3)不相同,则 j = next[j],返回第一步继续比较。
    在这里插入图片描述

    这里理解比较困难,需要仔细想想:next 数组和部分匹配值之间的关系,以及给子串添加一个字符。

获取 Next 数组的代码(Java)

// 在前面的分析中,模式串下标是从 1 开始
// 而计算机存储中,串的下标都是从 0 开始的,故这里的 next 数组是分析的结果 -1
// next 中存的是“匹配”失败后,回溯后模式串的下标
public static int[] getNext(char[] s) {
    // 开始构建 kmp 的 next 数组
    int[] next = new int[s.length];

    int i = 0, j = -1;

    next[0] = -1;

    while (i < s.length - 1) {
         if (j == -1 || s[i] == s[j]) {
             next[++i] = ++j;
        } else {
            j = next[j]; // 回溯
        }
    }

    return next;
}

KMP 算法的优化 —— Nextval 数组

图解 Nextval 数组的核心思想

    通过前面的分析,对于这样一个模式串,可以求的其 next 数组为 0 1 1 2 3 4。
在这里插入图片描述
    那么请思考以下这个场景:在某一次匹配中,在 i = 4 的匹配失败,即如下图所示:
在这里插入图片描述
    如果根据 next 数组的回溯规则进行回溯,那么就会变成以下的情况:
在这里插入图片描述
    那么请思考,这一次比较的结果有什么意义吗?
    总结:我们既然在 i = 4 的时候与 “b” 匹配发生了失败,是不是就可以确认主串中对应的值必定不为 “b”,因此我们可以根据这个特征对 next 数组进行改造,即为 nextval 数组。

详解 Nextval 数组

具体的步骤和求 next 数组差不多,只是多了一个当前字符和 next 数组对应值的比较的步骤

获取 Nextval 数组的代码(Java)

// 在前面的分析中,模式串下标是从 1 开始
// 而计算机存储中,串的下标都是从 0 开始的,故这里的 next 数组是分析的结果 -1
// nextval 中存的是“匹配”失败后,回溯后模式串的下标
public static int[] getNextval(char[] s) {
    // 开始构建 kmp 的 next 数组
    int[] nextval = new int[s.length];

    int i = 0, j = -1;

    nextval[0] = -1;

    while (i < s.length - 1) {
        if (j == -1 || s[i] == s[j]) {
            ++i;
            ++j;
            if (s[i] != s[j]) {
                nextval[i] = j;
            } else {
                nextval[i] = nextval[j];
            }
        } else {
            j = nextval[j]; // 回溯
        }
    }
    return nextval;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值