超简单KMP算法及代码解释

一、假如你已经知道一些大概,只用求next数组的话。

我们假设next[k]表示以k结尾的最长前后缀相等长度,

(这样的话,我们在和主串比对的时候,子串j只移动到 前j-1段的相等前后长度next[j-1]的下一个位置与主串i比对,即j = next[j-1])

那么我们怎么求解next数组呢?先看代码

vector<int> calNext(const string &sub) {int n = sub.size(), k = 0;

​        vector<int> next(n, 0);for(int i = 1; i < n; ++i) {while(k > 0 && sub[i] != sub[k]) k = next[k-1];if(sub[i] == sub[k]) ++k;

​            next[i] = k;}return next;
}

1、i = 0时前后缀最长k是等于0,即next[0] = 0.

2、如果i位置以前的最长前后缀长度k的下一位等于当前i上字符,最长k+1;

​ 否则因为前缀的下一个不能相等, 则即从前缀中寻找前缀,再比较下一位,注意前缀是next[k-1].

举两个栗子:

1、
在这里插入图片描述

​ 假设我们正在计算next[3] :

​ 由于到2最长前后相等缀k = 1, 我们判别sub[k] == sub[3]?

​ 不等,那么判断k=next[k-1] = 0, sub[k] == sub[3]?,则next[3] = ++k = 1;

2、

在这里插入图片描述

​ 假设我们正在计算next[4]

​ 由于到3最长前后相等缀k=1, 判断sub[k] == sub[4] ? 相等,则next[4] = ++k = 2;

二、假如需要一些原理。

我们知道在求解是否存在子串问题时,暴力算法复杂度达到了O(mn), 而KMP算法只用O(m+n)。

其原因最本质的一点就是当主串遍历到i和子串遍历到j时,如果两者不等,i不必回退到i-j的下一个位置。

而是根据子串最长前后缀长度k, 判断子串下一个位置k上的字符是否与主串i位置相同。

达到只用移动子串上的指针不用移动主串指针。

证明:(为啥主串可以不用回移)假设当前不匹配情况下(s t r[i] != sub[j]),主串与子串的匹配初始点一定是在i-j后面某个位置p了,那么主串p到i上的字符一定要和子串前面一段字符匹配,所以i可以不后移,但j要回移了。

这个后移的量,即是子串sub以j-1结尾段的最长的前后缀相等长度。由于长度k的下标表示下一个位置,则后面只用判断sub[k]上字符是否与主串str[j]相同。

三、主程序

int m = str.size(), n = sub.size();if(n == 0) return 0;

​        vector<int> next = calNext(sub);int j = 0;for(int i = 0; i < m; ++i) { //其实下面一段代码计算和next数组基本一致。while(j > 0 && str[i] != sub[j]) j = next[j-1];if(str[i] == sub[j]) ++j;if(j == n) return i-n+1;}return -1;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值