KMP算法next数组的理解

本文详细解释了Next数组的概念及其计算过程。Next数组用于记录模式串中前缀与后缀最大相同字符个数,通过实例“abababac”展示了如何逐位计算Next数组的值,帮助读者理解其在字符串匹配算法中的作用。

next数组实际是储存某个位置前面的子串中前缀和后缀最大的相同字符个数

前缀:包括第一个字符的连续字符串。(真前缀,不包括自己)

后缀:包括最后一个字符的连续字符串。(真后缀,不包括自己)

例如:abababac

i = 0时, 子字符串为"a" 没有真前缀与真后缀, 所以next[0] = 0.

i = 1时, 子字符串为"ab", 没有共同的前后缀 , 所以next[1] = 0.

i = 2时, 子字符串为"aba", 最长的相同前后缀分别是"a"与"a" , 所以next[2] = 1.

i = 3时, 子字符串为"abab", 最长的相同前后缀分别是"ab"与"ab" , 所以next[3] = 2.

i = 4时, 子字符串为"ababa", 最长的相同前后缀分别是"aba"与"aba" , 所以next[4] = 3.

i = 5时, 子字符串为"ababab", 最长的相同前后缀分别是"abab"与"abab" , 所以next[5] = 4.

i = 6时, 子字符串为"abababa", 最长的相同前后缀分别是"ababa"与"ababa" , 所以next[6] = 5.

i = 7时, 子字符串为"abababac", 没有共同的前后缀, 所以next[7] = 0.

<think> 我们被要求回答关于KMP算法next数组算法函数。根据引用[3],有一个名为get_next的函数用于构建next数组。 函数原型:void get_next(SString T, int next[]) 功能:为模式串T生成next数组,其中next[i]表示当模式串中第i个字符失配时应该回退到的位置(实际上就是前i-1个字符的最长相等前缀后缀长度)。 函数流程: 1. 初始化:i=1, j=0, next[1]=0(因为第一个字符失配时,没有前缀,所以回退到0) 2. 循环(i从1到T.length-1,即i<T.length): a. 如果j==0(表示已经退回到起点)或者当前字符T[i]等于T[j](注意:这里字符串下标从1开始,T[1]是第一个字符): - i和j都自增1(i++, j++) - 然后设置next[i] = j(即当前最长相等前缀后缀长度为j) b. 否则(即当前字符不匹配且j不为0),则令j = next[j](利用已计算的next值回退) 注意:这个函数中,模式串T的下标是从1开始的(即T.ch[1]到T.ch[T.length]),而next数组的下标也是从1开始(next[1]对应第一个字符的next值)。 但是,引用[2]中给出了一个将nextnextval数组求解集成在一起的函数,并且使用了C++的string(下标从0开始)。因此,我们需要区分字符串下标起始位置。 根据引用[3]的描述,我们使用下标从1开始。但实际编程中,字符串下标通常从0开始。因此,我们可以有两种实现方式。 下面,我将给出两种版本的get_next函数:一种是按照引用[3]的描述(下标从1开始),另一种是适应C/C++中字符串下标从0开始的版本。 版本1:字符串下标从1开始(假设字符串T存储在T.ch[1..length]中,T.ch[0]不使用) 版本2:字符串下标从0开始(使用标准C++的string,但调整算法) 由于用户没有指定具体使用哪种字符串存储方式,我将分别说明。 但是,引用[2]中的代码使用了标准C++的string(下标从0开始),并且同时计算了nextnextval数组。如果用户只需要next数组,我们可以单独提取计算next数组的部分。 根据引用[3]的描述,我们重点实现get_next函数(仅计算next数组)。 我们按照引用[3]的代码逐行解析,并给出一个完整的函数。 注意:在引用[3]中,函数参数是SString T(可能是自定义的字符串结构),但为了通用性,我们可以用数组和长度代替。 下面,我给出两种实现: 实现1(下标从1开始): 假设字符串T[1..len](长度为len),next数组大小为len+1(next[0]不使用,从1到len使用)。 函数: void get_next(char T[], int next[], int len) { int i = 1, j = 0; next[1] = 0; while (i < len) { // 因为i从1开始,所以当i<len时,循环处理到i=len-1,然后i++后为len,然后设置next[i](即next[len]) if (j == 0 || T[i] == T[j]) { i++; j++; next[i] = j; } else { j = next[j]; } } } 实现2(下标从0开始): 这种情况下,我们需要调整:next[0]表示第一个字符失配时的回退位置,通常设为-1(或者0,但这里我们按照引用[2]中的设置,即j=-1开始)。 引用[2]中的代码是同时计算nextnextval的,但我们可以单独计算next数组(下标从0开始): void getNext(string T, int next[]) { int len = T.length(); int i = 0, j = -1; next[0] = -1; while (i < len - 1) { // 因为下面有++i,所以当i=len-1时,循环内会++i到len,然后设置next[len],但next[len]实际上不需要(因为匹配时不会在len处失配),所以通常计算到len-1 if (j == -1 || T[i] == T[j]) { i++; j++; next[i] = j; } else { j = next[j]; } } } 注意:在实现2中,循环条件是i < len-1,这样当i=len-2时,进入循环后i++变成len-1,然后设置next[len-1];如果希望计算到next[len](虽然一般用不到),可以循环到i<len,但是这样在最后一次循环中,i=len-1,然后i++变成len,然后设置next[len]=j+1(但通常next数组只需要0到len-1)。 实际上,在KMP算法中,我们只需要0到len-1的next值(因为模式串最后一个字符的下标是len-1,失配位置只可能出现在0到len-1)。所以实现2中,我们通常计算到next[len-1]即可。但上面的代码如果循环到i<len,那么next[len]也会被赋值(虽然不会用到)。所以两种循环条件都可以,只要保证不越界。 为了清晰,我们按照引用[2]中的初始化(j=-1)和循环条件(i<len)来实现,并计算到next[len](但注意,模式串长度为len,那么next数组需要len+1个元素,因为当模式串第len个位置失配时,需要next[len](即最后一个字符失配时的回退位置))。 但是,在KMP匹配过程中,当模式串已经匹配到最后一个字符(即j已经等于len-1)且匹配成功,那么不会使用next[len];如果匹配到最后一个字符时失配,那么就会使用next[len](即回退到next[len]的位置)。所以next[len]是有意义的。 因此,实现2(下标从0开始)的完整函数为: void getNext(string T, int next[]) { int len = T.length(); int i = 0; // 当前正在计算next[i]的位置,初始化时i=0,但我们会先递增i int j = -1; // 前缀指针 next[0] = -1; // 第一个字符失配时,j回退到-1(即主串指针后移,模式串从头开始) while (i < len) { // 循环直到i=len-1,然后进入循环后i会变成len,然后设置next[len] if (j == -1 || T[i] == T[j]) { i++; j++; next[i] = j; // 这里当i=len时,赋值next[len]=j,但此时i已经等于len,所以数组next的大小至少为len+1 } else { j = next[j]; } } } 但是,注意这个函数在循环中当i=len-1时,进入循环后执行i++变成len,然后赋值next[len]=j,这会导致数组越界(因为next数组大小通常为len,下标0到len-1)。所以为了避免越界,我们可以调整循环条件为i < len-1,这样就不会计算next[len]了。或者,我们保证next数组的大小为len+1。 通常,在KMP算法中,我们只使用0到len-1的next值,所以可以不计算next[len]。因此,循环条件改为i < len-1,这样i最大到len-2,然后i++后为len-1,然后赋值next[len-1]=j+1。 所以,更常见的版本(下标从0开始)是: void getNext(string T, int next[]) { int len = T.length(); next[0] = -1; int i = 0, j = -1; while (i < len - 1) { if (j == -1 || T[i] == T[j]) { i++; j++; next[i] = j; } else { j = next[j]; } } } 这样,next数组的下标范围是0到len-1。 总结: 根据引用[3],我们重点介绍了下标从1开始的get_next函数(因为引用[3]中字符串下标从1开始)。但在实际编程中(如C++的string),我们使用下标从0开始,所以上述第二种实现(循环条件为i<len-1)更常用。 此外,引用[2]中给出了同时计算nextnextval的代码,但用户只询问next数组,所以我们不展开nextval。 现在,我们根据引用[3]的描述,给出一个完整的get_next函数(下标从1开始)的代码(使用C语言风格,假设字符串T从1开始存储): ```c // 假设T[0]不使用,T[1..len]存储模式串,next数组大小为len+1(下标0..len,但0不使用,使用1..len) void get_next(char T[], int next[], int len) { int i = 1, j = 0; next[1] = 0; while (i < len) { if (j == 0 || T[i] == T[j]) { i++; j++; next[i] = j; } else { j = next[j]; } } } ``` 对于下标从0开始的字符串,我们给出C++版本的getNext函数: ```cpp #include <string> #include <vector> using namespace std; void getNext(string T, vector<int>& next) { int len = T.length(); next.resize(len); // 确保next数组大小为len next[0] = -1; int i = 0, j = -1; while (i < len - 1) { if (j == -1 || T[i] == T[j]) { i++; j++; next[i] = j; } else { j = next[j]; } } } ``` 注意:在C++中,我们通常使用vector作为数组容器,或者使用动态数组。这里使用vector传递next数组,函数内调整大小。 另外,也可以使用数组指针,但需要提前分配好内存(大小为len)。 最后,我们解释一下这个函数: - 当模式串第0个字符失配时,next[0]=-1,表示模式串需要整体后移(主串指针后移,模式串指针j从-1变成0,相当于模式串从0位置重新匹配)。 - 当匹配过程中,T[i]和T[j]相等时,说明可以继续扩展前缀后缀,所以同时后移i和j,并记录next[i]=j(即最长相等前缀后缀长度为j)。 - 当不相等时,j回退到next[j](即之前已经匹配的部分的最长相等前缀后缀位置)。 这样,我们就得到了next数组。 为了帮助理解,举个例子: 模式串T = "abab" 下标从0开始: next[0] = -1 i=0, j=-1 -> 进入if(j==-1成立),i=1, j=0 -> next[1]=0 i=1, j=0 -> T[1]='b', T[0]='a',不相等 -> j=next[0]=-1 i=1, j=-1 -> 进入if(j==-1成立),i=2, j=0 -> next[2]=0 i=2, j=0 -> T[2]='a', T[0]='a',相等 -> i=3, j=1 -> next[3]=1 i=3, j=1 -> T[3]='b', T[1]='b',相等 -> i=4, j=2 -> 循环条件i<len-1(4<3?)不成立,退出。 所以next数组为:[-1, 0, 0, 1] 验证:模式串"abab"的next数组(下标0开始): 位置0:-1 位置1:0 位置2:0 位置3:1 这个结果与常见的next数组一致。 因此,我们提供了两种实现,并重点推荐下标从0开始的版本(因为更符合现代编程习惯)。 如果用户需要nextval数组,可以参考引用[2]中的代码(在计算next数组的同时计算nextval),但用户的问题只要求next数组,所以我们不展开。 </think>KMP算法中的`next`数组是模式匹配的核心组件,其函数实现如下(基于引用[3]的解析): ### 下标从1开始的`get_next`函数实现(C语言风格) ```c void get_next(SString T, int next[]) { int i = 1; // 主指针(从模式串第1位开始) int j = 0; // 前缀指针 next[1] = 0; // 初始化首位next值 while (i < T.length) { // 遍历整个模式串 if (j == 0 || T.ch[i] == T.ch[j]) { // 情况1:匹配成功或j已回溯到起点 i++; j++; next[i] = j; // 记录当前最长相等前后缀长度 } else { // 情况2:失配时回溯前缀指针 j = next[j]; } } } ``` ### 下标从0开始的优化版(C++风格) ```cpp void getNext(string T, int next[]) { int len = T.length(); int i = 0, j = -1; // j初始化为-1 next[0] = -1; // 首位特殊标记 while (i < len - 1) { if (j == -1 || T[i] == T[j]) { i++; j++; next[i] = j; // 核心递推关系 } else { j = next[j]; // 失配回溯 } } } ``` ### 关键点解析 1. **核心作用** `next[i]`存储的是模式串前`i-1`个字符的**最长相等前缀后缀长度**。当第`i`位失配时,模式串指针回退到`next[i]`位置(引用[3])。 2. **递推关系** 通过已计算的`next[1..i-1]`推导`next[i]`: - 若`T[i] = T[j]`:直接扩展前后缀 → `next[i+1] = j+1` - 若失配:`j`回溯至`next[j]`,利用已匹配信息避免重复比较(引用[3])。 3. **时间复杂度** 虽然含双重循环,但`j = next[j]`保证均摊时间复杂度为$O(m)$($m$为模式串长度)。 ### 示例说明 模式串`T = "ababc"`的`next`数组计算过程: | $i$ | $T[i]$ | $j$ | 操作 | $next[i]$ | |-----|--------|------|---------------------|-----------| | 0 | a | -1 | 初始化`next[0]=-1` | -1 | | 1 | b | 0 | `T[1]≠T[0]`→回溯 | 0 | | 2 | a | -1 | `j=-1`→`i++,j++` | 0 | | 3 | b | 0 | `T[3]=b=T[0]?`→否→回溯 | 1 | | 4 | c | 1 | `T[4]=c≠T[1]`→回溯 | 2 | 最终`next[] = [-1, 0, 0, 1, 2]`(引用[2][3])。 ### 应用场景 - 字符串匹配加速(主串指针不回溯) - 文本编辑器的查找/替换功能 - 生物信息学中DNA序列匹配 - 循环节检测(如`next[len] > 0`表明存在周期) --- ### 相关问题 1. 为什么KMP的`next`数组能避免暴力匹配的主串回溯? 2. 如何优化`next`数组生成`nextval`数组?其优势是什么? 3. KMP算法与BM算法在模式匹配中的性能差异有哪些? 4. 如何利用`next`数组判断字符串周期性问题? [^1] [^2] [^3] [^4]
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值