Kmp字符串算法(每周一类)

        今天我们来讲Kmp算法,这个算法我个人认为真的很巧妙,具体的题目是这样的。

        AcWing Kmp字符串 

        

        第一步,整理清楚题意,就是有一个长字符串S,我们要从字符串S中找出小字符串P的位置,输出出来。

        第二步,用简单暴力的方法做出来,就是用双重循环,以接近O(m*n)的时间复杂度做出。

具体代码如下:

#include <iostream>
using namespace std;

const int N = 100010, M = 1000010;
char s[M], p[N];
int n, m;

int main(){
    cin >> n >> p >> m >> s;
    for(int i = 0; i < m; i ++ ){
        bool st = true;
        int k = i;
        for(int j = 0; j < n; j ++ ){
            if(s[k] != p[j]){
                st = false;
                break;
            }
            k ++;
        }
        if(st == true){
            printf("%d ",i);
        }
    }
    return 0;
}

        其实就是用暴力枚举的方法,在s中每一个位置都对字符串p进行匹配,如果匹配到了就进行输出。如果不那么追求效率,或者不要求百分百全过,这也是个不错的省力好办法。

        第三步,对之前的算法进行优化,我们这道题建议用Kmp,简单来说Kmp的思想就是省略相同折叠字符串的检索时间。

        代码如下

#include <iostream>
#include <algorithm>
using namespace std;

const int N = 100010, M = 1000010;
char p[N], s[M];
int ne[N];

int main(){
    int n,m;
    cin >> n >> p + 1 >> m >> s + 1;
    for(int i = 2, j = 0; i <= n; i ++ ){
        while(j && p[j + 1] != p[i]) j = ne[j];
        if(p[j + 1] == p[i]) j ++;
        ne[i] = j;
    }
    for(int i = 1, j = 0; i <= m; i ++){
        while(j && p[j + 1] != s[i]) j = ne[j];
        if(p[j + 1] == s[i]) j ++;
        if(j == n){
            printf("%d ",i - n);
            j = ne[j];
        }
    }
    return 0;
}

        如图

       Kmp的核心是next数组,通俗来讲,它是一个指针数组,指向上一个重复的部分。

        举个例子:当检索到c的时候匹配失败,p字符串会退回到之前重复的部分匹配,而不是从头开始匹配。比如上面图中的P字符串,ababa,下标分别是12345,下标从1开始,第一个a的重复部分显然没有,所以next[1] = 0,之后的b显然也没有重复的部分 next[2] = 0,第二个a可以找到第一个a作为重复的部分 next[3] = 1,第二个b可以找到ab作为重复的部分,next[4] = 2,之后next[5] = 3。这就是next数组的原理。

        我们的p的下标是j,s的下标是i。但在检索的时候我们常常习惯用j+1和i相互匹配。如图,p[5]与s[5] 不匹配,即a和c不匹配,也就是p[j + 1] 和 s [i] 不匹配,这时的 j 是4。所以,要退回到p[1],p[j + 1]和 s [5] 也不匹配,所以,退到p[0],p[0]没有字符,这时就跳过,从s[6]与p[1]开始匹配,之后一一匹配,直到匹配成功。

        现在来看代码,第一个for循环是来寻找p数组中的ne数组,也就是最长的重复循环。while(j && p[j + 1] != p[i]) j = ne[j]; 就是说在 j 不等于0,并且在p[j + 1]与p[ i ]不匹配时,就将 j 变成ne[ j ],也就是跳回上一个重复的位置(意思就是就向前找可以匹配的位置,直到找到,或者将 j 变成0,也就是回到初始的位置)。if(p[j + 1] == p[i]) j ++;如果匹配成功的话,就让 j 去到下一个位置。ne[i] = j; 将 j 变成 i 的循环位置(由于之前的两步已经保证了当 j + 1满足条件,就跳转到 j + 1的位置,如果 j + 1不满足的话,就向前找,直到找到,或者将 j 变成0,也就是回到初始的位置

        接下来看第二个循环,这个循环和之前的循环如出一辙,但作用却不太相同,这个循环是用来匹配 p串和 s 串的。 while(j && p[j + 1] != s[i]) j = ne[j]; 进行 j = ne[ j ]操作,直到 j 不为0,或者匹配失败。if(p[j + 1] == s[i]) j ++; 如果匹配成功,那么就将 j 移动到下一个位置。

        if(j == n){
            printf("%d ",i - n);
            j = ne[j];
        }如果匹配成功,就输出 i - n的位置(由于下标是从1开始的)。

        以上就是Kmp的详解,刚开始看Kmp的时候可能会有一点抽象,但如果深究下来,其实也不是很难,还是熟能生巧的事。

        刚开始第一次做还是建议将代码理解个大概,将每一句代码的含义弄明白,之后背下来,到可以轻松默写的程度,保证可以应用就ok了,至于原理不用太深究。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值