算法补天系列之——KMP算法,即字符串匹配算法

什么是KMP呢?

我们有一个string1,又有一个string2,这个时候我们想知道string2是不是string1的子串。这就是KMP
首先我们说经典算法,其实时间复杂度是比较大的,一般就是O(n*m)
其实KMP的主要思路和经典暴力方法还是一样的,但是KMP通过了一定的策略实现了加速。

首先我们要知道前缀和和后缀和

这个字符前面字符串中前后缀的最大匹配长度。记得不要取到整体

我们要这个干什么呢?是要给string2求的,string2的每一个字符都需要这个信息。我们把这个数据存储在next arr数组中。这里怎么求我们先不说,我们知道了这个简单求法先用着,看KMP过程有了这个数组之后怎么加速

开始执行KMP算法

两个string比较,我们到了string1的第n个位置和string2的i位置,我们发现没有对上,我们是不是就要从string的n+1位置和string2的0位置继续比,两个string双双回跳。

而现在我们知道了i之前的匹配情况,我们把string2的比较位置回跳到后缀和的位置。
这里特别抽象。我们细说。

我们之前是string1的0位置(比如说)开始比的,而此时到了n发现了匹配不上,但是我们此时不用叫string2从头去比,因为前后缀有一样的部分,这个时候我们将后缀当做前缀,我们从前缀后面开始去比,因为后缀和前缀一样,并且string1已经匹配好前缀了,后缀不用比也知道一样。
在这里插入图片描述

以此为例,经典过程我们最后发现没对上怎么办,是不是要从string1的b开始,再去和string2的a比。

而此时我们知道string2前缀后缀有一定的一样的位置abbstk,那么此时我们是要从string1的第二个abbstk之后和string2前缀之后的位置开始比。而我们知道这个时候string1之前是已经指定配不出来了,所以我们可以大胆把string2挪过去。

但是为什么呢?为啥一定配不出来了呢?

我们假设能配出来,是不是在错误之前的部分,至少得和string2的前半截得能配上,然后我们发现假设不成立,因为这样就意味着string2要找到一个更长的后缀。和我们之前next存在冲突。

核心思路

KMP的核心思路就可以说是string1死活不回跳。String2只要能往前跳就往前,不能跳了,说明前后缀直接用完也没匹配上,也可以理解为string2在不断的右移,此时下一轮比较节省的越来越少。string1就往下走走一个我们重新一轮再比。

KMP看来真的是挺麻烦的对吧。。不过代码确实是异常的简单。

我们就是要找i2越界,只有一路对比一路加走完越界才说明找到了。
一般来讲时间不超过2n次,所以也可以说就是常数级别的。

最后我们来看next数组怎么求就好了。

首先我们知道0,1位置分别是-1,0.
其次我们知道n号位置是需要前面n-1个next数组共同计算的就可以。就是当a[next[n-1]+1]=a[n-1]的时候,next[n]=next[n-1]+1,如果没有对上就要往前看了,这里往前看下一次比的就是a[next[next[n-1]]],这样一次次知道不能再往前了。那就0,往后走。

实现代码:

#include <iostream>
#include <bits/stdc++.h>

using namespace std;


vector<int> getnextArray(string s)
{
    vector<int> nextarray;
    if(s.length()==1){
        nextarray.push_back(-1);
        return nextarray;
    }
    nextarray.push_back(-1);
    nextarray.push_back(0);
/*
我们知道n号位置是需要前面n-1个next数组共同计算的就可以。
就是当a[next[n-1]+1]=a[n-1]的时候,next[n]=next[n-1]+1,
如果没有对上就要往前看了,这里往前看下一次比的就是a[next[next[n-1]]],
这样一次次知道不能再往前了。那就0,往后走。
*/
    //这个变量的意义就是遍历到的字符要去和谁去比,顺便可以记录之前的前后缀重合程度
    int biao=0;
    for(int i=2;i<s.length();){
        //这里要说明的一点就是我们要求的next数组是这个字符之前的部分的前后缀
        //所以是i-1
        if(s[i-1]==s[biao]){
            //这里就是成功借助了之前的积累
            nextarray.push_back(++biao);
            i++;
        }
        //这个地方就是因为我们是第biao个没有匹配上,但是因为biao之前的部分也有自己的前后缀
        //所以这一次我们放低标准,缩短了前后缀匹配长度,再去比
        else if(biao!=0){
            biao=nextarray[biao];
        }
        //这个意思就是biao=0了,就是两个比一下都没对上,那就没救了,下一个了。
        else{
            nextarray.push_back(0);
            i++;
        }
    }
    return nextarray;

}


int getindexof(string a,string b)
{
    //这里的意思就是说必不能找到子串的几种可能。
    if(a.length()==0||b.length()==0||a.length()<b.length()){
        return -1;
    }
    //也算是双指针吧
    int biao1=0;
    int biao2=0;
    vector<int> nextay=getnextArray(a);
    //这里其实如果只是为了遍历的话,前面的条件就够了,不过这里附加了一个找到跳出的条件
    //这里也是KMP算法最精髓的地方
    while(biao1<a.length()&&biao2<b.length()){
        //能匹配上,自然是皆大欢喜,我们继续往下走
        if(a[biao1]==b[biao2]){
            biao1++;
            biao2++;
        }
        //接下来看一下如果没有匹配上,按照我们之前的老思路,就是biao1++,biao2=0了
        //但是这样是浪费了不少时间的
        //所以这里我们利用之前得到的前缀和进行了一下调整。
        /*
        KMP的核心思路就可以说是string1死活不回跳。
        String2只要能往前跳就往前,不能跳了,说明前后缀直接用完也没匹配上,
        也可以理解为string2在不断的右移,此时下一轮比较节省的越来越少。
        string1就往下走走一个我们重新一轮再比。
        */
        //这里就是说明已经没救了,这个点位是绝对匹配不上了,换下一个点
        else if(nextay[biao2]==-1){
            biao1++;
        }
        //这里就是虽然我们之前匹配了半截之后断了,但是我们还是有一小截能匹配上的
        //所以就还是可以少匹配一些
        else{
            biao2=nextay[biao2];
        }
    }
    //这里就是看一下从循环是怎么出来的,如果是找到了出来的,返回找到的起始点,反之-1
    return biao2==b.length()?biao1-biao2:-1;
}

int main()
{
    string s;
    cin>>s;
    string t;
    cin>>t;

    /*
    vector<int> nextay=getnextArray(s);
    for(int i=0;i<nextay.size();i++){
        cout<<nextay[i]<<endl;
    }
    */
    cout<<getindexof(s,t)<<endl;
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值