学习日记:2022年2月8日

今天写的第一个题目kmp

具体的题目我也没有看,直接到网上搜索了一下kmp算法

首先kmp算法解决的问题就是从一个母串中快速查找到子串的位置

题目的要求也刚好就是找出子串在母串中的位置

以题目中的例子来说

已知一个母串
ABABABC

已知一个子串

ABA

第一种方法,暴力破解,也是第一次看见这个题目最先会想到的方法

拿母串进行一个一个比较,如果完全匹配那么就输出

第一次匹配

能够完全匹配,就输出一个1,移动到下一个

第二次匹配

 第一个就不对,继续移动 

第三次匹配

 成功匹配,输出3,继续移动

后面的第四次匹配,第五次匹配都不成功

使用这样的方法来匹配的话,需要匹配的次数就是5

如果我们使用kmp算法来求的话过程是这样的

首先第一次匹配

输出1

第二次匹配

输出3

第三次匹配

 最终匹配的次数就是3

其中的算法当然也不是就是找开头相同这么简单

我们再换一个例子

已知母串

已知子串 

第一次匹配

第二次匹配

 第三次匹配,这个时候就会和我们最开始的暴力破解不一样了

首先我们定位到不相同的点,也就是编号为9的位置,首先我们可以确保9之前的位置都是能够匹配的,接下来我们就要找一个位置,接下来就是找能够匹配的子串里面是否有后缀串等于前缀串,如果有,我们就匹配前缀串的后一个

能够匹配的子串为:ABCDAB,前缀等于后缀的刚好也有AB=AB,移动子串,使之前缀串的后一个进行匹配

 第四次匹配,上次匹配的位置是9,而9之前的位置都是匹配了的,这个就是kmp算法的妙处

我们继续前一步操作,此时没有能够相等的前缀串和后缀串了。于是我们就把子串的开头移过来

 第五次匹配

 最终成功匹配

这个就是kmp算法的思路

其中最重要的就是遇到不匹配的位置如何进行跳转

我最开始的思路就是用另外一个数组来对子串进行一个类似于定位的操作

我们把子串ABCDABD拆开来看

如果我们第一个就错了,我们就需要往后移动一个

如果是AB第二个B错了,我们需要定位到A的位置,所以B定位的位置是0(从0开始)

如果是ABC第三个C错了,我们需要定位到A的位置,C定位的位置是0

如果是ABCD第四个D错了,我们还是要定位到A的位置重新开始,D的定位位置也是0

当ABCDA的第五个A错了,我们定位的位置依旧是A的位置,所以A定位的位置也是0

当ABCDAB的第六个B错了,我们定位的位置就是最前面B的位置了,可以参考一下前面的例子。所以当前的B定位的位置就是1了

当ABCDABD的第七个D错了,前后缀都没有相同的,所以定位的位置也是0

最终出来的一个定位表格就是

A        B        C        D        A        B        D

0        0        0        0          0        1        0

这个也是题目中结果的最后一行要填的东西

但是使用这种方法就会发现ABA得出来的是000

这个就是这个题目和网上的内容不同的地方

题目中的定位包含当前的位置,而网上的大多都不包含当前的位置。

总体来说,包含会有一个-1的烦恼,不包含要好一些

这个题目的难点也是吧这个表格给计算出来

如果是abababc

第一个肯定是0

第二个也是0

第三个能和前面的a匹配也就是1

第四个能够匹配出一对ab,也就是2

第五个能够匹配出一对aba,也就是3

第六个能够匹配出一对abab,也就是4

第七个没有匹配,也就是0

最终的出来的结果就是这样子

 如果找规律的话不难发现,如果能够匹配的话就是前一种情况加上当前的字母

从第三个开始a,ab,aba,abab

如果和前面不匹配的情况如何呢???

比如果ababcccababc

前面的直接跳过,我们直接来到不能匹配的地方

 下标为5的时候不能够和前者形成匹配,于是我们就找到前者的前者是否能够匹配(回溯,判断是否相等)前者为4,回溯的位置为2,不相等,继续回溯,最终达到了0依然不相等,所以标记为0

使用同样的方法,来到下一个特殊的点

 最后一个点,下标为12的时候,首先不能和前者进行匹配,

前者为11,不相等,回溯,回溯的位置为5,b!=a

继续回溯,回溯的位置为3和前者a相等,最终定位的点为最后一次回溯的位置也就是3

最终的代码如下

#include<iostream>
#include<string.h>
using namespace std;
char mon[1000006];//母串
char son[1000006];//子串
int pla[1000006];//前缀长度
int prepare()//求前缀最长border长度
{
    pla[0]=0;
    int len=strlen(son);
    int present=1;
    while(present<len)
    {
        //判断是否和前者匹配
        if(son[present]==son[pla[present-1]])
        pla[present]=pla[present-1]+1;
        else
        {
            int front=pla[present-1];
            // 定位到能够匹配的点
            while(son[front]!=son[present]&&front)
            {  
            // cout<<front<<endl;
                front=pla[front-1];
            }
            //如果能够匹配
            if(son[front]==son[present])
            pla[present]=front+1;
            else    
            pla[present]=0;
        }
        present++;
    }
    return 0;
}
int check()
{
    int present=0;
    int len=strlen(mon);
    // cout<<len<<endl<<endl<<endl;
    for(int i=0;i<len;)
    {
        //是否遇到了不相同的节点
        if(son[present]!=mon[i])
        {
            // cout<<present<<' '<<i<<endl;
            //如果第一个就不匹配的情况
            if(present==0)
            i++;

            //匹配完成的情况
            if(present==strlen(son))
            {
                cout<<i-present+1<<endl;
                present=pla[present-1];
            }

            //匹配还没有完成的情况
            else
            {
                present=pla[present-1];   
            }
            continue;
        }
        i++;
        present++;
    }

    // 判断最后一个
    if(present==strlen(son))
    {
        cout<<len-present+1<<endl;
    }
    return 0;
}
int main()
{
    cin>>mon>>son;
    prepare();
    check();
    for(int i=0;i<strlen(son);i++)
    cout<<pla[i]<<' ';
    return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值