KMP算法详解,3000字详解,带你学会next数组

字符串模式匹配(KMP)

前言

心路历程:KMP有名字,我不认识,万一写出来呢,那么简单,小小字符串匹配,还起个名?

——>超时了~

这三个大哥真厉害


给定一个字符串 text 和一个模式串 pattern,求 pattern 在text 中的出现次数。text 和 pattern 中的字符均为英语大写字母或小写字母。text中不同位置出现的pattern 可重叠。

输入格式:

输入共两行,分别是字符串text 和模式串pattern。

输出格式:

输出一个整数,表示 pattern 在 text 中的出现次数。

输入样例1:

zyzyzyz
zyz

输出样例1:

3

输入样例2:

AABAACAADAABAABA
AABA

输出样例2:

3

数据范围与提示:

1≤text, pattern 的长度 ≤106, text、pattern 仅包含大小写字母。

代码长度限制

16 KB

时间限制

500 ms

内存限制

64mb


我写的超时大垃圾代码:

我写的:

#include <iostream>
#include <string.h>
#include <string>
using namespace std;

int main()
{
    string test, pattern;
    getline(cin, test);
    getline(cin, pattern);
    int p1 = 0, p2 = 0, start, flag = 0, count = 0, con = 0;
    while (p1 < test.length())
    {
        p1 = con;
        while (1)
        {

            if (test[p1] == pattern[p2])
            {
                p2++;
                p1++;

                if (p2 == pattern.length())
                {
                    count++;
                    p2 = 0; // test从头开始
                    break;
                }
            }
            else
            {
                p2 = 0;
                break;
            }
        }
        con++;
    }
    cout << count << endl;
    system("pause");
}

请添加图片描述

然后我就搜了搜kmp算法,!这三个大神直接把乘法的时间复杂度改写成了加法,直接换了个符号!


KMP算法详解

kmp算法最主要的我认为并不是算法本身而是算法中的求next数组(这个地方我用了好几个小时看了好几个视频才理解。。。)


next数组的原理及代码

next数组就是指的是next数组前缀和后缀相等的之前的数出现的次数

代码:

void GetNextval(string p, int *next)
{
    next[0] = -1;
    int i = 0, j = -1;
    int length = p.length();
    while (i < length)
    {
        if (j == -1 || p[i] == p[j])
        {
            next[++i] = ++j; //后一位是前一位的多少多少
        }

        else
            j = next[j];
    }
}

带图详解:自认为详细

首先,我们设置0号位为-1,标记一下开头的话。

0号位之前肯定没有 那1号位肯定是0前面就有一个。

第一next点

我们还没设置next数组的情况,就是从0开始的情况会发生什么?

比如说 abc* 我们在进行配置数组的时候就会一个个的进行就是最初始的字符串

第二对称点

abab*会变成什么样呢,是-10012,在判断 *好时就是看b的next值为1如果

如要判断第五位因为第四位上的next值为1说明1号位上的前缀与三号位上的前缀相同此时i=3,j=1如果此时相同的话 五号位上的值就为2,代表着p【2】前面的那两位和p【4】前面的那两位拥有相同的前缀
请添加图片描述

第N对称点

如果这几个对称点连着就会变成上面那种情况如果不相连呢会发生什么呢,不相连的话会怎么办呢

1.根本不对称:

会一直next下去下降到0号位==-1这个没有对称点。

2.依然对称

会降低以及看看有没有相同的前缀对称没有的话会一直降到第1对称点就是第一前缀来比对是否有相同的前缀

下面我们来画个图来说明一下怎么求第i+1就会很直观了:

请添加图片描述

假设有16个字符,我们来求第十七位

这样我们就能知道对称点的个数了next坐标了,当后缀匹配不成功的时候我们就去离他最近的next的坐标看看。他后面的数是不是能匹配。

这样最最最最难的next数组就求出来了

  if (j == -1 || p[i] == p[j])
        {
            next[++i] = ++j; //后一位是前一位的多少多少
        }

这个代码主要是看看从哪一位起前缀和后一个的前缀相同如果相同next就增加一位表明从这一位起的前缀与我们后面i+1那一位之前的前缀是相同的


算法主体

算法主体就比较简单了我认为最难得就是求next数组

先晾代码:

int KmpSearch(char* s, char* p)
{
	int i = 0;
	int j = 0;
	int sLen = strlen(s);
	int pLen = strlen(p);
	while (i < sLen && j < pLen)
	{
		//①如果j = -1,或者当前字符匹配成功(即S[i] == P[j]),都令i++,j++    
		if (j == -1 || s[i] == p[j])
		{
			i++;
			j++;
		}
		else
		{
            //如果不是-1或者不相等
			//next[j]即为j所对应的next值,也就是前面介绍的前缀相同的位置   因为j的前缀和next[j]的前缀相同所以从这个的后面开始匹配就行   
			j = next[j];
		}
	}
	if (j == pLen)
		return i - j;
	else
		return -1;
}

几个问题:

  • 为什么j=next[j]就能行?,因为j的前缀和next[j]的前缀相同所以从这个的后面开始匹配就行;
  • 如果匹配完了有两种情况:1.匹配串j循环完了,这次就循环完了;2.匹配串没匹配完但被匹配串走完了,没有一个。所以如果j==plen就完事了就匹配完了
  • i-j就是这个串开始的坐标因为退出了,这个i暂停了减去串的长度s就是起始位置
  • 我觉得就很清楚了

开头题的答案

而这个题就是这个kmp算法的一个延伸

#include <iostream>
#include <string.h>
#include <string>

using namespace std;
int KmpSearch(string s, string p, int next1[])
{
    int i = 0;
    int j = 0;
    int sLen = s.length();
    int pLen = p.length();
    int count = 0;

        while (i < sLen)
        {
            //①如果j = -1,或者当前字符匹配成功(即S[i] == P[j]),都令i++,j++
            if (j == -1 || s[i] == p[j])
            {
                i++;
                j++;
            }
            else
            {
                //②如果j != -1,且当前字符匹配失败(即S[i] != P[j]),则令 i 不变,j = next[j]
                // next[j]即为j所对应的next值
                j = next1[j];
            }
            if (j == pLen)
            {
                count++;//加一个数然后让j从新开始匹配
                j = next1[j];
            }
        }
    
    return count;
}
//优化过后的next 数组求法
void GetNextval(string p, int *next)
{
    next[0] = -1;
    int i = 0, j = -1;
    int length = p.length();
    while (i < length)
    {
        if (j == -1 || p[i] == p[j])
        {
            next[++i] = ++j; //后一位是前一位的多少多少
        }

        else
            j = next[j];
    }
}
int main()
{
    string test, pattern;

    getline(cin, test);
    getline(cin, pattern);
    int next1[pattern.length()] = {0};
    GetNextval(pattern, next1);
    int count = KmpSearch(test, pattern, next1);
    cout << count << endl;

    system("pause");
}

在这里插入图片描述

哎终于学会了,收货良多~~~~~~~~~

  • 7
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

c0re

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

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

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

打赏作者

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

抵扣说明:

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

余额充值