KMP算法

KMP算法

1. 求解next数组

next数组的含义是:模式串 t 中,字符 t[j] 之前,最多有 k 个字符,与 t 开头的 k 个字符相匹配。

即:t[0] ~ t[k-1] 与 t[j-k] ~ t[j-1] 匹配。

这样的话,在子串匹配的时候:如果遇到 t[j] 与主串不匹配,只需要将 j 移动到 next[j] 的位置,然后继续匹配,不用移动到开头,且此时可以保证 j 之前的字符是匹配的。

求next字符的代码
void getNext(int* next, string t) {
    if (t.length() == 1) {
        next[0] = -1;
        return;
    }
    next[0] = -1; // 方便后面匹配用,人为定义
    int i = 0;
    int k = -1;
    while (i < t.length() - 1) {
        if (k == -1 || t[i] == t[k]) {
            i++;
            k++;
            next[i] = k;
        }
        else {
            k = next[k];
        }
    }
}
代码解释:

在每次循环中,都有 t[0] ~ t[k-1] 和 t[i-k] ~ t[i-1] 相匹配!!!

  1. 首先,如果 k == -1,表示找不到一个 k 使得 t[i + 1] 前面的部分和 t[0] ~ t[k-1] 匹配,那就令 next[i + 1] = 0,就是说下次匹配的时候,要从头开始匹配;

  2. 如果 t[i] == t[k],那么 next[i + 1] = k + 1;

  3. 为什么 next[k] = k; ?

    原因:

在这里插入图片描述

如图,现在要找 next[i+1],而 t[k] 和 t[i] 又不相等。

在本次循环中,红色区域已经匹配上了。然而,因为 t[k] 和 t[i] 不相等,所以 next[i + 1] 肯定小于 k,我们想要找的 next[i + 1]应该是绿色区域那部分(包括了 t[i])。要找到绿色区域这部分的匹配,可以先找到绿色区域中,不包括 t[i] 的那部分和字符串首部的最长匹配,即蓝色区域。蓝色区域这部分匹配怎么找呢?可以从图中看到,因为红色区域是完全匹配的,右边的蓝色区域,是绿色区域的头部,而正好是红色区域的尾部;左边的蓝色区域,又正好是红色区域的头部;所以要找的蓝色区域,即为红色区域头部和尾部的最长匹配,也即图中蓝色和黑色区域的最长匹配,也即找到 next[k]。因此,令 k = next[k],在下一轮判断中,又会判断 t[k] 是否等于 t[i],这时候就判断了绿色区域中出去蓝色区域的那剩下一个字符是否匹配,若匹配上了,next[i + 1]就等于这时候的 k + 1;若没有匹配上,就再找蓝色区域中的最长头尾匹配,更新 k,类似于递归操作了~

2. 求解nextval数组

有了next数组还不够,为了提高时间效率,还应该引入一个nextval数组。

为什么有了next数组还不够?

因为会出现这种情况,即 t[i] = t[next[i]],在主串和模式串匹配的时候,如果 t[i] 已经和主串不匹配了,又令 i = next[i],这时候就又多判断了一次,因为 t[next[i]] == t[i],肯定还是和主串不匹配,可以直接再让 i = next[i],如果这时候 t[next[i]] 还是等于 t[i],就再让 i = next[i],直到它俩不相等或者找到模式串头部。

所以,nextval数组的意义就在于实现上面说的事情。在模式串和子串开始匹配之前,就提前做好 t[next[i]] == t[i] 的准备,直接让 nextval[i] 的值等于 i 前可以与模式串首部最长匹配的,且 t[i] != t[k] 的 k 值。

代码如下:
void getNextval(int* next, int* nextval, string t) {
    nextval[0] = -1;
    if (t.length() == 1) {
        return;
    }
    
    if (t[0] == t[1]) {
        nextval[1] = -1;
    }
    else {
        nextval[1] = 0;
    }

    for (int i = 2; i < t.length(); i++) {
        int tmp = next[i];
        while (tmp != -1) {
            if (t[tmp] == t[i]) {
                tmp = nextval[tmp];
            }
            else {
                break;
            }
        }
        nextval[i] = tmp;
    }
}
完整代码:
#include<iostream>
using namespace std;

void getNext(int* next, string t) {
    if (t.length() == 1) {
        next[0] = -1;
        return;
    }
    next[0] = -1; // 方便后面匹配用,人为定义
    int i = 0;
    int k = -1;
    while (i < t.length() - 1) {
        if (k == -1 || t[i] == t[k]) {
            i++;
            k++;
            next[i] = k;
        }
        else {
            k = next[k];
        }
    }
}

void getNextval(int* next, int* nextval, string t) {
    nextval[0] = -1;
    if (t.length() == 1) {
        return;
    }
    
    if (t[0] == t[1]) {
        nextval[1] = -1;
    }
    else {
        nextval[1] = 0;
    }

    for (int i = 2; i < t.length(); i++) {
        int tmp = next[i];
        while (tmp != -1) {
            if (t[tmp] == t[i]) {
                tmp = nextval[tmp];
            }
            else {
                break;
            }
        }
        nextval[i] = tmp;
    }
}

int strStr(string haystack, string needle) {
    if (needle == "") {
        return 0;
    }
    int* next = new int[needle.length()];
    int* nextval = new int[needle.length()];
    getNext(next, needle);
    getNextval(next, nextval, needle);
    int i = 0, j = 0;
    while (i < haystack.length() && j < needle.length()) {
        if (haystack[i] == needle[j]) {
            i++;
            j++;
        }
        else {
            if (nextval[j] == -1) {
                j = 0;
                i++;
            }
            else {
                j = nextval[j];
            }
        }
    }
    delete[] next;
    delete[] nextval;
    if (j >= needle.length()) {
        return i - needle.length();
    }
    return -1;
}

int main() {
    cout << strStr("hello", "ll");
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值