欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

KMP模式匹配算法next数组值的推导详解

程序员文章站 2024-02-16 09:35:16
...

继上一篇关于模式匹配算法的博文,我想在这里在继续详细解说一下next数组和nextval数组值的推导。

为什么需要next数组

next数组到底是用于什么的?为什么要有next数组?
在这里我们用next数组来记录模式串各个位置的j值变换
让我们用一个例子来说明上述概念:
假设主串S=abcdefgab
匹配串为T=abcdex
如果我们按照BF模式匹配算法来处理这个模式匹配的问题,那么其对应的步骤应该如下图所示:(带颜色的方块表示第一个不相同的字符)
KMP模式匹配算法next数组值的推导详解如果我们仔细观察的话,这两个字符串在第一次匹配的时候,前5位都是相等的。
并且对于要匹配的子串来说首字母’a’与后面的串"bcdex"中任意一个字符都不相等。也就是说既然a不与自己后面的子串中的任何一个字符相等,那么对于图1来说,主串和子串的前五位字符都相等,意味着子串的首字符不可能与主串的第一位到第五位的任何一个字符相等,也就是说,在上图的2345步骤的判断都是多余且没有必要的,所以在这个算法中我们只需要保留第一步和第六步即可。

经过我们的分析我们会发现,在BF模式匹配算法中是通过不断地回溯控制主串首字符的i值来达到模式匹配的目的的,但是经过上述的分析我们发现这里的i值并没有回溯,由第一步到第六步i值并没有发生变化,也就是说这种回溯其实是没有必要的,我们的KMP模式匹配算法就是为了让这种没有必要的回溯不发生。
那么既然控制主串的i值不回溯了,我们如何让我们的算法聪明的自己知道应该直接由第一步跳转到第六步,不进行中间的对于步骤呢?这个时候我们便需要一个数组来帮助我们记录子串各个位置的j值变换了,这也就是next数组诞生的必要性了
举例:假如在模式匹配的时候j = 4的时候发现和主串已经出现了不匹配的情况,那么当j=3时我们的j值接下来应该跳转到哪一个值?这个时候我们便需要去查找next[j]了。

next数组值的推导

沿用上一篇推到next值的代码,在这里我将根据代码来讲next数组值的推导。

void get_kmp_next(char *string, int *next)
{
    size_t i = 1;
    int j = 0;
    next[1] = 0;
    while (i < strlen(string)) {
        if (j == 0 || string[i] == string[j]) {
            i++;
            j++;
            next[i] = j;
        } else {
            j = next[j];
        }
    }
}

以模式串“abcabx”为例:
KMP模式匹配算法next数组值的推导详解

nextval数组值推导

如果目标穿为aaaabcde
模式串为aaaaax,那么模式串的next数组值分别为012345
按照上面的KMP算法,我们将会得到以下的过程
KMP模式匹配算法next数组值的推导详解但我们会发现,2,3,4,5步完全是多余的判断,由于模式串的2,3,4,5位置的字符都与1位置的相等,那么我们就可以用1位的next数组值去取代与它相等的字符后续next[j]的值,我们将取代next数组的数组命名为nextval数组。

void get_kmp_nextval(char *string, int *nextval)                          
{                                                                   
    size_t i = 1;                                                   
    int j = 0;                                                      
    nextval[1] = 0;                                                    
    while (i < strlen(string)) {                                    
        if (j == 0 || string[i] == string[j]) {                     
            i++;                                                    
            j++;                           
            if (string[i] != string[j]) { //若当前字符与前缀字符不相同
                nextval[i] = j;// 则当前的j为nextval在i位置的值
            } else { 
                nextval[i] = nextval[j];                                                                                                                      
            }
        } else {        
            j = nextval[j];// 如果与前缀字符相同,则将前缀字符的nextval值赋值给nextval在i位置的值。
        }                                                           
    }                                                               
}

如有错漏,敬请指正。