BF算法与KMP算法

这两种算法都是字符串匹配算法,通俗来说,就是得到子串在主串中的位置。

BF算法:

BF算法是普通(简单)的查找算法,就是我们常说的暴力破解法。

基本思想:

  • 将目标串(主串)S的第一个字符与模式串(子串)T的第一个字符进行匹配,若相等,则继续比较S的第二个字符和 T的第二个字符;
  • 若不相等,则比较S的第二个字符和T的第一个字符,依次比较下去,直到得出最后的匹配结果。

图示:
在这里插入图片描述
分析:
从BF算法的思想可以得到他的时间复杂度为O(m*n),这是由于i指针一直要回退,但是有些情况不需要回退,就如上述图示情况,回退之后和子串T还是不匹配,所以这就导致了多次毫无意义的比较,浪费了大量时间。虽然这种方法简单易懂,但是时间复杂度太高,不适合在大规模的数据量下使用。

代码详解(c++):

```
#include<iostream>
#include<string>
using namespace std;

int BF(const string& S,const string& T,int pos)
{
    int len_s = S.size();
    int len_t = T.size();
    int i = pos;	//主串的位置
    int j = 0;	//子串的位置

    while (i < len_s && j < len_t)
    {
        if (S[i] == T[j])	//匹配
        {
            i++;
            j++;
        }
        else	//失配
        {
            j = 0;	//j归0
            i = i - j + 1;	//i回退
        }
    }
    if (j >= len_t)	//子串完成匹配,则找到
    {
        return i - j;
    }
    else
    {
        return -1;
    }
}

int main()
{
    string S;
    string T;
    int pos;
    cin >> S;
    cin >> T;
    cin >> pos;
    cout << BF(S,T,pos) << endl;

    return 0;
}
```

KMP算法:

KMP算法是对BF算法的改进,主要是改进了BF算法中,i指针回退的缺点,从而提高效率。

基本思想:

  • i不回退,但是j退到相应的位置k
  • 找到位置k后,比较主串i位置和子串k位置的字符,重复BF算法的步骤即可;

由此可见,KMP算法的核心及难点就是找到j回退的位置k

算法核心yi’xi(找j回退的位置k):
1、那么j应该回退多少呢,回退到那个位置呢?那我们就用我们人的思维来考虑j回退后的位置;
在这里插入图片描述
由上述推论可得出找到j回退后的位置k的方法:
在子串T中,在匹配成功的“子串”中找到两个最长的相等的前缀子串和后缀子串,两个子串有如下特点:

  • 一个子串以0下标作为开头;
  • 另一个子串以失配前的最后一个字符作为结尾。

所以这两个最长的相等的真子串的长度就是k会退后的位置。

2、用我们人的思维推出了找到j回退后的位置k的方法,那么我们如何将这种方法应用到代码中,让计算机识别这种方法呢?
因为子串T的每一个位置都可能发生不匹配,也就是说我们要计算每一个位置j对应的k,所以用一个数组next来保存,next[j] = k表示当 S[i] != T[j] 时,j指针的下一个位置。
在这里插入图片描述
next数组的实现:

  • next数组中,next[0] = -1,next [1] = 0; next[0] = -1,这是因为如果第一个字符就失配,则需要i++j不需要移动;next [1] = 0,这是因为第二个字符之前就只有一个字符,j必须回到0位置重新匹配。
  • 当T[k] == T[j]时,next[j+1] = next[j]+1; T[0 ~ k] == T[j-k ~ j],即next[j+1] == k + 1 == next[j] + 1。
  • 当T[k] != T[j]时,k = next[k]; 此时T[k] != T[j],k继续回溯前缀(此时的k就相当第一次失配时的j),相当于重复k = next[j]的过程。
    static int *Git_next(const string& T)	//创建next数组
    {
        int len = T.size();
        int *next = new int[len];
        next[0] = -1;
        next[1] = 0;
    
        int j = 0;
        int k = -1;
        while (j < len-1)
        {
            if (k == -1 || T[k] == T[j])	//next[j+1] == k+1
            {
                next[++j] = ++k;
            }
            else
            {
                k = next[k];	//k继续回溯前缀(此时的k就相当第一次失配时的**j**),相当于重复k = next[j]的过程。
            }
        }
        return next;
    }```
    

3、如上方法,看似已经很完美了,但是还存在一定的瑕疵
在这里插入图片描述

  • 如图所示,如果再j = 3处失配,则k = next[j] = 1,如果j回溯到k位置,但T[k] = b,仍然失配;
  • 这就可以看出此回退是无效的,k还得继续回退,即k = next[k],所以我们就让k一次回退到位;
  • 重新计算每一个位置j对应的k的最终回退位置,再用一个数组nextval来保存,**nextval[j] = nextval[next[j]];**表示当 T[j] == T[T[j]] 时,j指针的下一个位置。

所以我们将nextval数组里保存的值就叫修正后的 k,即就是j的最终回退位置,修正后的代码如下:

static int *Git_nextval(const string& T)
{
    int len = T.size();
    int *next = new int[len];//初次k值数组
    int *nextval = new int[len];//修正后k值数组
    next[0] = -1;
    next[1] = 0;

    int j = 0;
    int k = -1;
    while (j < len-1)
    {
        if (k == -1 || T[k] == T[j])	//next[j+1] == k+1
        {
            next[++j] = ++k;
        }
        else
        {
            k = next[k];	//k继续回溯前缀(此时的k就相当第一次失配时的**j**),相当于重复k = next[j]的过程。
        }
    }

    nextval[0] = -1;
    for (int i = 1; i < len; i++)
    {
        if (T[i] == T[next[i]])		//T[i] == T[k],这样导致回退后的位置k不是j的最终回退位置。
        {
            nextval[i] = nextval[next[i]];	//一次性回退到位
        }
        else	//T[i] != T[k]
        {
            nextval[i] = next[i];	//说明k已回退到位
        }
    }
    delete[]next;
    return nextval;
}

算法分析:
KMP算法相比于BF算法最主要的一点就是i不需要回溯了,它的时间复杂度可以达到O(m+n)。
它的核心其实就是next数组,只要理解了next数组KMP算法也就算是解了。

有些人一想到KMP算法就会觉得很难,就害怕了,越只要害怕就越不想去搞懂它,所以这个问题就一直得不到解决,我之前也是这样。但是这一次我硬着头皮,花了两天时间,终于搞懂了KMP算法,只要静下心,下功夫,就没有解决不了的问题。
如果想了解详细代码请看我的GitHub:https://github.com/dong1102/Programming-Exercises/blob/master/BF和KMP算法.md

推荐一篇讲KMP算法的文章,写的还不错https://www.cnblogs.com/yjiyjige/p/3263858.html。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值