KMP算法分析—next数组求解思路详解

本文详细介绍了KMP算法中的核心概念——next数组的构建过程,通过具体的例子和伪代码解析了如何根据字符串动态求解next数组,并讨论了当新增字符时如何更新next值。文章适合对KMP有一定了解的读者,旨在帮助理解KMP算法的难点和逻辑。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


前言

KMP算法比较晦涩难懂,本文主要记载我对KMP算法的理解以及思路,主要参考代码随想录和网上大神的求解思路,如有错误望指正。
本篇推文主要面对有一定基础的读者,适用于对KMP有一定了解的读者。


一、next数组

我们知道KMP算法的核心就是求解next数组,next数组就是前缀表(本推文不介绍减一的next数组,只介绍将前缀表当作next数组的情况),而前缀表求的是最长相同前后缀的长度,因此next数组的定义比较简单,但是使用代码求解有一定难度,对于代码随想录中的求解方法我有点不太理解,因此我采用了网上其他大神的求解思路,可能具体思路大致一样,但是代码具体实现思路不一致,下面介绍我对求解next数组的理解。
我们其实可以将next数组的求解问题看作一个类似于动态规划的问题,next[i]表示下标i的最长相同前后缀的长度(本文记作len),求解思路如下:

用一个具体例子进行解释:
以a a b a a为例,很显然next[0]=0,next[1]=1,next[2]=0,next[3]=1,next[4]=2
如果我们对这个字符串新增加一个字符,那么next[5]等于多少呢?肯定是要根据具体的情况进行讨论

在介绍具体思路之前我们还需要明确一点就是next数组的含义,前面我们介绍过next[i]表示下标i的那个字符的最大共同前后缀长度len,那么其实这个len表现在字符串中我们可以理解为,len是指向最长前缀的下一个字符,是什么意思呢,我举个例子说明一下:

以字符串str=“aabaa”为例
next[0]=0,next[1]=1,next[2]=0,next[3]=1,next[4]=2
我们分析next[4]=2,即最长共同前后缀长度为2,即为字符串”aa“,而如果将len作为一个索引的化,那么len就是指向最长共同前缀的
下一个字符即”b”,意思就是str[len]=“b“,就是这么简单。

我们了解到len的具体含义之后,就可以继续之前的讨论,之前我们提到,如果对一个字符串新增加一个字符,那么新增加的这个字符的len是多少?,具体情况如下:

还是以字符串”aabaa”为例,大致可以分为两种情况:
1,增加的字符=“b“
2,增加的字符!=”b”

为什么要和b进行比较呢,其实很容易理解,因为next[4]=2,那么代表着这个字符串的前两个字符和最后两个字符是一样的,现在我要新加一个字符,那么这个字符只需要和第三个字符进行比较即可,如果相同,那么len直接加1即可,如果不同再进行讨论。但是这里存在了一个问题那就是我怎么知道每次要和谁进行比较,这次是和第三个字符进行比较,下次由于len的改变就不是和第三个字符进行比较了,这里就要用到之前我们提到的len的意义了,len指向的就是最长共同前缀的下一个字符,在“aabaa”这种情况下,len指向的不正是字符“b“吗,所以我们便有了以下伪代码:

if(str[len]==str[i])
{
	len++;(这个len表示当前字符串末尾下标的最长共同前后缀长度)
	next[i]=len;
	i++;
}
else
{
	其他处理;
}		

以上就是当新添加的字符与len指向的字符一样的情况,如果不一样的,这里我们又要进行讨论,具体原因如下:

这次我们多举几个例子从中发现规律
以字符串”aabaa"为例,我们新添加一个字符c
那么此时的next[4]=2,next[5]=0;
以字符串”ababcabab"为例,我们新添加一个字符a
此时的next[8]=4,next[9]=3
继续以字符串”ababcabab"为例,我们新添加一个字符d
此时的next[8]=4,next[9]=0
以字符串”aabac"为例,新添加字符a
那么此时的next[4]=0,next[5]=0;
以字符串”aabaac"为例,新添加字符a
那么此时的next[5]=0,next[6]=0;

很明显我们可以知道当添加的字符和len指向的字符不一样时,可以分为两种情况,那就是len是否为0,如果len为0,那么不论你添加什么字符,你的len还是0,不会变,但是如果len不为0,这是求解next数组最难理解的地方,下面我会进行详细分析,先把len=0的伪代码写出来:

if(str[len]==str[i])
{
	len++;
	next[i]=len;
	i++;
}
else
{
	if len==0
		next[i]=0;
		i++
	else
		另行讨论;
}	

接下来讨论len不为0的情况,我还是举具体例子进行理解

以字符串”ababcabab"为例,我们新添加一个字符a
那么next[9]等于多少和谁有关呢,这也是很难理解的地方,下面我进行详细分析
首先我们加了一个不等于len指向的字符的字符a,新的字符串为ababcababa
那么其实求向字符串”ababcabab"增加字符a的len就是求向字符串”abab“新增加字符a的len,原因如下:

由于a!=c,而已知abab为next[8]的最长公共子序列,此时新增加一个不等于c的字符,那么next[9]的最长共同前后缀序列一定小于4,
即最长相同前后缀一定是ababa(最后的这个a代表的就是新增加的a)中的一部分。
此时我们就可以知道求next[9]就是求ababa的next[4],即求向字符串”ababcabab"增加字符a的len就是求向字符串”abab“新增加
字符a的len。

现在我们就知道了求解next[9]就是求解在abab上新添加一个字符的最长前后缀长度,这又是一个新的求最长前后缀长度的问题,这不就进入了循环吗,我们只需要再按照前面的思路再进行一次判断,先判断这次的字符和len指向的字符是不是一样,注意此时的len改变了,不再是ababcabab的len,而是abab的len,此时的len的计算方法也很简单,就是下标为len-1的最长前后缀长度,不就是next[len-1]。因此求解next数组的代码如下:

while(i<str.size())
	if(str[len]==str[i])
		{
			len++;
			next[i]=len;
			i++;
		}
	else
	{
		if len==0
		{
			next[i]=0;
			i++;
		}
		else
			len=next[len-1];
}	

二、代码及运行结果

有了next数组之后,KMP算法就比较简单了,代码我直接给出:

#include<iostream>
#include<vector>
using namespace std;

class Solution {
public:
    void getNext(vector<int> &next, const string& s) {
        int len = 0;
        next[0] = 0;
        for (int i = 1; i < s.size(); i++) {
            if (next[len] == next[i])
            {
                len++;
                next[i] = len;
                i++;
            }
            else
            {
                if (len == 0)
                {
                    next[i] = 0;
                    i++;
                }
                else
                {
                    len = next[len - 1];
                }
            }
        }
    }
    int strStr(string haystack, string needle) {
        if (needle.size() == 0) {
            return 0;
        }
        vector<int> next(needle.size(), 0);
       	cout << "字符串" << needle << "在字符串" << haystack << "中第一次出现的位置是:" << endl;
        getNext(next, needle);
        int j = 0;
        for (int i = 0; i < haystack.size(); i++) {
            while (j > 0 && haystack[i] != needle[j]) {
                j = next[j - 1];
            }
            if (haystack[i] == needle[j]) {
                j++;
            }
            if (j == needle.size()) {
                return (i - needle.size() + 1);
            }
        }
        return -1;
    }
};


int main()
{
    Solution s;
    string haystack = "saaseasad";
    string needle = "sad";
    cout << s.strStr(haystack, needle);
    return 0;
}

运行结果如下:、

在这里插入图片描述


总结

以上就是我对KMP算法的理解,KMP算法的难点在于如何建立next数组,本文主要针对next数组的建立进行分析,希望对读者有所帮助,如果文中有错误的地方,望指正。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值