AcWing算法基础课-BF和KMP算法

1,305 阅读3分钟

本文未作过多专业讲解,仅用于自身学习时不理解地方的记录

一、BF 算法

又名朴素的模式匹配算法,和我们人的思考模式基本是一样的。如果匹配成功则两个指针都加一,如果匹配失败,则指向主串回溯至下一个位置,指向子串的回溯到零位置。
这里为了方便string的使用,采用下标从零开始。

BF实现代码如下

int indexOneDF(string s,string t){
    int i=0,j=0;//i指向s,j指向t
    while(i<s.size() && j<t.size()){
        if(s[i] == t[j]){
            i++;j++;//如果相等则移动到下一个
        }else{
            //根据j的值,来确定i向前移动了多少,回到上一次的位置的下一个字符
            i = i-j+1; 
            j=0;
        }
    }
    if(j>=t.size()){
        return i-t.size();//返回匹配到的主串的第一个位置
    }else{
        return -1;
    }
}

二、KMP 算法

又名模式匹配算法,和上述朴素算法不同的是,KMP算法在移动到下一个位置的时候不是无脑的将子串对齐到下一个位置,而是利用一个next数组。
next[i]表示:next[0, i] 之间的最长公共前后缀的长度, 它代表的实际意义是指向最长前缀的下一位。

为什么 j 回退到next[j-1]就可以计算出最长的前后缀?

本段部分参考自 juejin.cn/post/694547…
本文章使用动图的方式展示了KMP的过程,直接的展示了KMP算法的实现过程。

假设此时 前缀指针j 和 后缀指针计算到了如下情况, 在j=7和i=14时发生了失配. 而之前的最长前后缀是ABCDABC, 所以需要找到此时next[0, i] 之间的新的最长前后缀是什么.

image.png

这时我们 把 前缀指针 j 回退到 next[j-1]的位置 , 如下图. next[j-1] 就表示next[0, j-1]之间的最长前后缀的长度(即指向最长前缀的下一位), 此时最长前缀X0子最长前后缀分别是x1 和 x2 区域, 同理最长后缀Y0子最长前后缀就是y1 和 y2, 而又因为X0 和 Y0 是匹配的, 所以有 X1 和 y2 是匹配的 所以此时再去比较 j 和 i 的值就可以知道此时next[0, i] 之间的最长前后缀的长度. 如果相等最长公共前后缀的长度就是 j + 1, 如果还是不相等, 继续回退j 重复上述过程.

image.png

再次回退后, j 和 i 还是不想等, j 回退到0, 还是不想等. 所以next[i] = 0。

image.png

KMP实现代码如下

这里为了使用string,均采用下标从0开始,不推荐。

/*
 * @Author: IndexYang 
 * @Date: 2021-11-28 12:40:01 
 * @Last Modified by: IndexYang
 * @Last Modified time: 2021-11-28 15:39:35
 */
 
#include <iostream>
#include <cstring>
using namespace std;
const int N = 100010;

void KMPfromZero(string s,string t){
    int m=s.length(),n=t.length();
    //m为主串的长度,n为待匹配字串的长度
    int ne[N];//next数组,即当前位置需要向后移动多少
    ne[0] = -1;//重要!!
    for(int i=1,j=-1;i<n;i++){
        //如果j已经移动,且j的下一个与i不等,则向后移动
        while(j>=0 && t[i] != t[j+1]) j=ne[j];
        //如果j的下一个等于i,则j++
        if(t[i] == t[j+1]) j++;
        //将j值
        ne[i] = j;
    }
    for(int i=0,j=-1 ;i<m ;i++){
        while(j!=-1 && s[i]!=t[j+1]) j=ne[j];
        if(s[i] == t[j+1]) j++;
        if(j == n-1){
            //如果成功走到最后
            printf("%d ",i-n+1);
            j = ne[j];
        }
    }
}
int main(){
    string s,t; //s为主串,t为待匹配的串
    cin>>s>>t;
    //朴素算法
    //cout<<indexDF(s,t)<<endl;
    //KMP算法
    KMPfromZero(s,t);
    return 0;
}

这里是使用字符串数组,将串的储存从数组下标1开始,方便计算使用。

// KMPfromOne
#include <iostream>
using namespace std;
const int N = 100010, M = 1000010;

int n, m;
int ne[N];
char s[M], p[N];

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