• 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 
    #include 
    #include 
    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 
    #include 
    #include 
    
    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");
    }
    

    在这里插入图片描述

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

  • 相关阅读:
    kettle spoon连接MySQL8.0数据库报错解决方法
    DSP2335的按键输入key工程笔记
    [Rust学习:三] 循环和切片
    Git命令快速入门(建议收藏)
    Enzo丨Enzo AMPIVIEW HPV 6/11 RNA探针组方案
    Cocos Creator3.8 项目实战(九)2D UI DrawCall优化详解(下)
    png转pdf怎么转换?这些图片格式转换工具确定不来看看?
    oracle学习79-oracle之单行函数之分组函数
    代码以功能为单位
    算法练习-LeetCode 剑指 Offer 16. 数值的整数次方
  • 原文地址:https://blog.csdn.net/m0_63830846/article/details/127044087