KMP查找子串

  • 关于字符串匹配
    aabaaaaabbaaabaabaabbbaa找出是否有子串aabaab,有则返回开头下标,无则返回-1。
1)暴力匹配

假设主串长度为n,子串长度m。x为当前主串上的下标,y为当前子串的下标。
在这里插入图片描述
在这里插入图片描述
每次不匹配后,x要往前挪,回到原来位置的下一个位置。y当然也会回到初始位置。那么整个下来的时间复杂度O(n*m)。

2)KMP算法

首先需要一个next数组,next数组存储的是子串上各位置的最长前后缀长度。如下面的子串aabaab。
在这里插入图片描述
算法规定0位置值为-1,1位置值为0。
那么接下来我们看2位置,2位置前面的字符串为aa,因为前后缀长度不可以等于该字符串本身的长度,所以2位置的值为1(最长相同前后缀为a)。
3位置,前面的字符串为aab,无相同前后缀,该位置值为0。
同理,4位置值为1,5位置值为2。
在这里插入图片描述
先不管怎么得出这个next数组,代码怎么写,我们先看看有了这个next数组,怎么优化子串匹配的过程。
主串aabaaaaabbaaabaabaabbbaa,子串aabaab。
在这里插入图片描述
X,y从0开始,如果当前值str1[x] == str2[y],那么x,y都往后移动。知道来到了这个位置,值不相等。我们都知道,如果暴力遍历的话,x会回到下标1位置,而y会回到下标0位置。
但KMP算法借助next数组,我们可以做到x不用再往回挪动,只需要y往回挪。
在这里插入图片描述
我们上面求到了str2的next数组,得知next[y]等于2,那么就把y = 2。
在这里插入图片描述
因为前后缀相等,所以可以看成子串str2直接往后移动,那么前两个位置是肯定匹配的。
在这里插入图片描述
以这种方式一直往后移动,如果next[y] = -1,即y已经回到了下标0位置,那么就换成x往后移一格。
那么什么时候匹配完成呢。一种是当y等于子串str2长度的时候,那么就是匹配到子串否则就没有匹配到。
整个过程:
1.当x<str1.size() && y<str2.size()时循环

1.1.如果 str1[x] == str2[y] ,则 x,y都往后移,加1

1.2.如果不等,有两种情况:
如果next[y] == -1,表示y回到下标0位置,此时x++
如果 next[y] != -1,则y = next[y]

int KMPgetpos(string array,string str,string next)
{
    if(array.size() == 0 || next.size() == 0)
        return -1;

    size_t x = 0,y = 0;
    while(x<array.size() && y<next.size())
    {
        if(array[x] == str[y]){
            x++;
            y++;
        }else if(next[y]!=-1){
            y = next[y];
        }else{
            x++;
        }
    }
    return y == next.size()?x-y:-1;
}

·求取next数组
0位置和1位置固定是-1和0。直接上代码。

string getnext(string str)
{
    string next(str.size(),0);
    if(str.size()==1)
        return next;

    next[0] = -1;
    next[1] = 0;
    int cnt = 0;
    size_t i = 2;
    while(i<str.size()){
            if(str[i-1] == str[cnt]){
                next[i++] = ++cnt;
            }else if(cnt != 0){
                cnt = next[cnt];
            }else{
                next[i++] = 0;
            }
    }
    return next;
}
  • 题目:
    一串字符中aabaabbbaaabbabbaaabbaaaababb,要使该字符串不含子串aaba。若含则把该子串改为aaa,问要修改多少次。

1)分析
在通常情况下,在主串中找到有多少子串,则修改次数就是找到的子串数。但存在这样一种特殊的情况,如aababa,找到子串aaba,修改后主串为aaaba,又有子串aaba,但若使用KMP算法,此时x应该来到了b的位置了,所以会一直往下,从而主串仍有aaba,但没有统计到。
对于把aaba删成aaa,对于原序列的改动太大,把aaba变成aaaa即可,那么主串变成aaaaba,x只需要回调到x-2的位置即可。

int KMPgetpos(string array,string str,string next)
{
    if(array.size() == 0 || next.size() == 0)
        return -1;
    int cnt = 0;
    size_t x = 0,y = 0;
    while(x<array.size())
    {
        if(array[x] == str[y]){
            x++;
            y++;
            if(y == str.size())
            {
                array[x-2] = 'a';
                x = x - 2;
                y = 0;
                cnt++;
            }
        }else if(next[y]!=-1){
            y = next[y];
        }else{
            x++;
        }
    }
    //return y == next.size()?x-y:-1;
    return cnt;
}

string getnext(string str)
{
    string next(str.size(),0);
    if(str.size()==1)
        return next;

    next[0] = -1;
    next[1] = 0;
    int cnt = 0;
    size_t i = 2;
    while(i<str.size()){
            if(str[i-1] == str[cnt]){
                next[i++] = ++cnt;
            }else if(cnt != 0){
                cnt = next[cnt];
            }else{
                next[i++] = 0;
            }
    }
    return next;
}

int main()
{
    string str1 ="aababababbaaabbabbaaabbaaaabababaabbbbabbbabbbaaaab";
    string str2 = "aaba";
    string next = getnext(str2);
    cout<<KMPgetpos(str1,str2,next)<<endl;
//    cout<<str1<<endl;
    cout<<str1.size()<<endl;
    return 0;
}

在这里插入图片描述

  • 题目
    有一个字符串 让你找到这个字符串 S 里面的子串T 这个子串 T 必须满足即使这个串的前缀 也是这个
    串的后缀 并且 在字符串中也出现过一次的(提示 要求满足前后缀的同时也要在字符串中出现一次 只是前后缀可不行 输出最长满足要求字符串)在这里插入图片描述

分析:本题直接利用next数组即可,不用进行匹配,我们先看看上述字符的next数组有什么特征。
在这里插入图片描述
当字符串符合条件时,其next数组有以上特征,next数组m位置即为最大子串的长度。那么用一个临时的数组array进行标记,在0< i <m,array[next[i]]++,那么如果中间无该子串,那么array[next[m]]该位置为0,若有则为1. 如果尾部无该子串,那么next[m]应该为0,所以可以以此为依据跳出循环。

#include <iostream>
#include <string>
using namespace std;
int mynext[1000001];
int vis[1000001];
string str;

void func()
{
    mynext[0] = -1;
    mynext[1] = 0;
    int cnt = 0;
    int i = 2;
    while(i<=str.size())
    {
        if(str[i-1]==str[cnt])
        {
            mynext[i++] = ++cnt;
        }else if(mynext[cnt]!=-1){
            cnt = mynext[cnt];
        }else{
            mynext[i++] = 0;
        }
    }
    for(int i = 1;i<str.size();i++)
        vis[mynext[i]]++;
    
    int m = str.size();
    bool flag = false;
    while(mynext[m]){
        if(vis[mynext[m]])
        {
            for(int i = 0;i<mynext[m];i++)
            {
                cout<<str[i];
            }
            cout<<endl;
            flag = true;
            break;
        }
       m = mynext[m];
    }
    if(flag == false)
        cout<<"Just a legend"<<endl;
    return ;
}

int main()
{
    cin>>str;
    func();
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值