【数据结构】KMP

目录

         一:简介

        二:实现

1.暴力     O(n*m)

2.kmp    O(n*m)

          为什么cin>>a+1

三:next数组的解释

四:优化


一:简介

 KMP算法是一种字符串匹配算法,可以在 O(n+m)的时间复杂度内实现两个字符串的匹配
所谓字符串匹配,是这样一种问题:“字符串 P 是否为字符串 S 的子串?如果是,它出现在 S 的哪些位置?” 其中 S 称为主串;P称为模式串。

 

如果暴力就是O(n*m),两个for循环,ij同时动。

二:实现

1.暴力     O(n*m)

法一【stl】:调用substr

int match(string text, string pattern) {
    int n = text.length(), m = pattern.length();
    for (int k = 0; k < n - m + 1; k+=1) {
        if (text.substr(k, m) == pattern) {
            return k;
        }
    }
    return -1;
}

法二【正常】: 

//暴力匹配法
    public static int violenceMatch(String s1,String s2){
        char[] str1=s1.toCharArray();
        char[] str2=s2.toCharArray();
        int i=0;
        int j=0;
        while (i<str1.length && j<str2.length){
            if(str1[i]==str2[j]){
                i++;
                j++;
            }else {
                //没匹配到就把i移动到刚开始和j匹配的后一位
                i = i - j + 1;
                j = 0;
            }
        }
        if(j==str2.length){
            return i-j;
        }
        return -1;
    }

 

2.kmp    O(n*m)

#include <bits/stdc++.h>
using namespace std;

#define MAXLEN 225

typedef struct
{
    char ch[MAXLEN];
    int length;
} String;

void get_next(String T,int next[])
{
    int i=1,j=0; //i领先一个位置
    next[1]=0;
    while(i<T.length)
    {
        if(j==0||T.ch[i]==T.ch[j]) //如果j=0时,意味着当前i无法和模式串匹配,i需要向后移动
        {
            i++;
            j++;             //i++相当于next[j]赋值给next[j+1],j++相当于移动到可以直接匹配的位置
            next[i]=j;
        }
        else
        {
            j=next[j]; //回退
        }
    }
}

int  Index_KMP(String S,String T,int next[])
{
    int i=1,j=1;
    while(i<=S.length&&j<=T.length)
    {
        if(j==0||S.ch[i]==T.ch[j])
        {
            i++,j++;//匹配成功,i和j向后移动
        }
        else
        {
            j=next[j];//j回调可以直接匹配的位置

        }
    }
    if(j>T.length) return i-T.length; //不需要+1,因为while1循环退出之前i++,用来while循环判断了
    else return 0;
}



int main()
{
    String s1,s2; //s1母串,s2子串
    char a[MAXLEN],b[MAXLEN];
    cout<<"输入母串:"<<endl; //aabaabaabaabaabaaaa
    cin>>s1.ch+1;
    s1.length=strlen(s1.ch+1);
    cout<<"输入子串:"<<endl;//aabaabaaaa
    cin>>s2.ch+1;
    s2.length=strlen(s2.ch+1);
    int next[MAXLEN];
    get_next(s2,next);
    cout<<"next数组如下"<<endl;
    for(int i=1;i<=s2.length;i++)
    {
        cout<<next[i]<<" ";
    }
    cout<<endl;
    printf("匹配位置:%d\n",Index_KMP(s1,s2,next));



    return 0;
}

 为什么cin>>a+1

char a[10];
cin >> a + 1;

什么意思呢,也就是说这是一个指针运算,a表示的是指针char*,由于>>操作优先级低于+,所以先进行+1运算,a+1表示的是&a[1]这个地址,所以说cin>>a+1也就是从地址&a[1]处开始写输入,相当于舍弃了a[0]这个值。果然a[0]处乱码,由于从a[1]开始写,于是只有a[1], a[2], a[3]保存着我们输入的有意义的字符。

而上文中next数组要求从next[1]开始,所以cin>>s1+1;  那么strlen中s1也要加1或者总length-1

三:next数组的解释

  1.  j是短字符串的指针,i是长字符串;
  2. next数组只看短字符串,next[j]=1j-1的最长前后相等缀+1
  3. 默认next[1]=0;next[2]=1;

如下图:j=4,j前面即j-1到1的子串为aba,最长前后相等缀为a即1,next[4]=1+1=2

               j=6,j前面即j-1到1的子串为abaab,最长前后相等缀为ab即2,next[6]=2+1=3

四:优化

puls优化nextval[j]数组:

        当已经出现过一个字母时,eg:a,则令nextval[3]=next[1]=0

                                                    eg:b,则令nextval[4]=next[2]=1 

        j指针就不用重复遍历已出现的a、b了

这里写的是直接改好的val数组

#include <bits/stdc++.h>
using namespace std;

#define MAXLEN 225

typedef struct
{
    char ch[MAXLEN];
    int length;
} String;



int  Index_KMP(String S,String T,int next[])
{
    int i=1,j=1;
    while(i<=S.length&&j<=T.length)
    {
        if(j==0||S.ch[i]==T.ch[j])
        {
            i++,j++;//匹配成功,i和j向后移动
        }
        else
        {
            j=next[j];//j回调可以直接匹配的位置

        }
    }
    if(j>T.length) return i-T.length; //不需要+1,因为while1循环退出之前i++,用来while循环判断了
    else return 0;
}

void get_nextval(String T,int nextval[])
{
    int i=1,j=0;
    while(i<T.length)
    {
        if(j==0||T.ch[i]==T.ch[j])
        {
            i++,j++;
            if(T.ch[i]!=T.ch[j])
            {
                nextval[i]=j;  //如果不相等正常赋值
            }
            else
            {
                nextval[i]=nextval[j]; //相等时
            }
        }
        else
        {
            j=nextval[j];
        }
    }
}


int main()
{
    String s1,s2; //s1母串,s2子串
    char a[MAXLEN],b[MAXLEN];
    cout<<"输入母串:"<<endl; //aaabaaaab
    cin>>s1.ch+1;
    s1.length=strlen(s1.ch+1);
    cout<<"输入子串:"<<endl;//aaaab
    cin>>s2.ch+1;
    s2.length=strlen(s2.ch+1);
    int next[MAXLEN];
    get_nextval(s2,next);
    cout<<"nextval数组如下"<<endl;
    for(int i=1; i<=s2.length; i++)
    {
        cout<<next[i]<<" ";
    }
    cout<<endl;
    printf("匹配位置:%d\n",Index_KMP(s1,s2,next));



    return 0;
}

动画演示

  • 14
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 9
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

漆黑の莫莫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值