浅谈KMP

# 再探KMP

### 算法分析
KMP算法的时间复杂度应该是O(n + m)的;其中n是模式串p的长度,m是字符串s的长度;
因为遍历模式串p求next数组的时间复杂度为O(n),遍历s对p进行匹配的次数为O(m);对于字符串匹配的推导过程
首先是BF算法

```
#include <iostream>

using namespace std;

const int N = 1e5 + 10, M = 1e6 + 10;

int main() {
    char p[N], s[M];
    int n, m;

    cin >> n >> m;
    cin >> p + 1 >> s + 1;

    for (int i = 1; i <= m - n + 1; i++) {
        bool flag = true;
        for (int j = 1; j <= n; j++) {
            if (s[i + j - 1] != p[j]) {
                flag = false;
                break;
            }
        }
        if (flag) cout << i << ' ' << i + n - 1 << endl;
    }
}
```

可以看到BF算法的时间复杂度为O(n * m);
有神人优化出了KMP算法:
我们通过求出模式串数组p的前缀长度ne数组
ne[i] 表示的为p[1, i] 中最长前后缀的长度

求ne[i]的过程为先用模式串p对其自身进行匹配;
设其被匹配的为p1,进行匹配的为p2;
其逻辑为:
1、让p2后移一位用p1[i] 与 p2[j + 1]进行匹配, 这里使得i严格大于j
2、匹配中p1[k] != p2[j + 1] 使用j = ne[j](已经被计算过);
3、匹配过程中有三种匹配情况 

  1. 1. j = 0 时,j被回溯到0, 或刚开始匹配时,p1[i] != p2[j + 1] 语句将不会被执行, 所以当找到p[i] == p[j + 1]需要if来判断
  2. 2. j = k时,并且回溯完没有被回溯到0, if判断多余
  3. 3. j = n时,表示整个串都被包含,虽然在找ne数组时不会发生,但在p与s匹配时会出现;


#include <iostream>

using namespace std;

// p[N] 是模式串(即p在s中多次以子串出现), s[M] 为字符串;
const int N = 1e5 + 10, M = 1e6 + 10;

char p[N], s[M];
// ne[i] 记录的是使p[N]中前 i 个字符形成的子串的最长前后缀的长度
int ne[N];

int main() {
    int n, m;// n 为模式串p的长度,m为字符串s的长度;
    //将字符数组数组预处理成从1开始的字符串;
    cin >> n >> p + 1 >> m >> s + 1;

    //在匹配过程中我们的j 一直是落后于 i的 i对比的一直是j + 1;
    for (int i = 2, j = 0; i <= n; i++) {
        //当我们的i遍历到第k个点的时候, 如果 j 未回溯到开头并且发现p[i] != p[j + 1]
        //我们让j 回溯到 ne[j]再进行匹配, 因为ne[j] 中记录的就是p[1, j] 中最长前后缀的长度
        while (j && p[i] != p[j + 1]) j = ne[j];

        //匹配成功让j继续向下走 
        // 为什么用if 因为有可能前面j回溯到了最前面 而循环结束的; 
        if (p[i] == p[j + 1]) j++;

        //记录一下ne[i], 表明p[1, i]中的最长前后缀长度为j;
        ne[i] = j;
    }

    for (int i = 1, j = 0; i <= m; i++) {
        while (j && s[i] != p[j + 1]) j = ne[j];

        if (s[i] == p[j + 1]) j++;

        if (j == n) {
            //有的题下标从0开始有的题下标从1开始;
            cout << i - n << ' '; // i - n + 1是下标从1开始的;
            j = ne[j];
        }
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值