详解KMP算法中的前缀表计算


目的

本文不是为了详解KMP算法的整个流程,而只是教大家如何理解求解前缀表的过程。如果前缀表能顺利写出来,那么字符串匹配就十分好理解了。


一、背景

先简单补充一点名词解释:

  1. KMP算法:求解子串在父串中是否出现以及第一次出现的位置的算法。
  2. 前缀:以第一个字符开头、不包含最后一个字符的所有连续子串均称为前缀;
  3. 后缀:以最后一个字符结尾、不包含第一个字符的所有连续子串均称为后缀;
  4. 最长相等(公共)前后缀:前缀子串 = 后缀子串长度最长
  5. 前缀表:记录下标i之前(包括i)的字符串的最长相等前后缀的长度

二、求解前缀表

1. 关键

我觉得很重要的一点,也是很多文章都没有说明白的一点,就是:假设str[0, i] 是我们要求最长相等前后缀的字符串,那么,它的最长相等前后缀的长度是与str[0, i - 1]密切相关的!

正是这种依赖关系,导致我们在查找str[0, i]的最长相等前后缀的长度时,只需比较str[i] 与 str[ vNext[i - 1] ] 的关系即可得出结论

这里面,
vNext[i - 1] 表示 str[0, i - 1] 的最长相等前后缀的长度;
str[ vNext[i - 1] ] 表示 str[0, i -1] 的最长相等前缀的后一个字符,即第一个不匹配字符。

所以,求解前缀表的过程,就是在比较新加入的字符str[i] 与 第一个不匹配字符str[ vNext[i - 1] ] 是否相等?如果不相等,则下一个不匹配字符缩短最长相等前后缀的长度,再次比较缩短后的最长相等前后缀的第一个不匹配字符。

2. 模拟

对于字符串 aabaac ,推理过程如下:
1)当 i = 0 时,str[0,i] = a ,没有前后缀,则最长相等前后缀为0,则 vNext[0] = 0,令 k = vNext[0] = 0 来记录 str[0, i - 1]的最长相等前后缀的长度;
2)当 i = 1 时,str[0,i] = aa ,此时比较新加入的字符str[i] = str[1] = a上一个子串的第一个不匹配字符 str[k] = str[0] = a, 两者相等,则str[0, i] 的最长相等前后缀 比 str[0, i -1]的最长相等前后缀 多 1,则 vNext[i] = k + 1 = 1, 同时更新 k = vNext[i] = 1;
3) 当 i = 2 时,str[0, 2] = aab,此时比较新加入的字符str[i] = str[2] = b上一个子串的第一个不匹配字符 str[k] = str[1] = a, 两者不相等,说明:
– a) str[0, i] 的最长相等前后缀的长度比str[0, i -1] 的短
– b) str[0, i] 的最长相等前后缀只可能出现在str[0, i -1] 的最长相等前后缀里面。str[0, i -1] 的最长相等前后缀 = str[0, vNext[k - 1]]。这里 k - 1 是因为假设长度为2,则 index= 2 - 1 = 1

综上两点,k = vNext[k - 1] = vNext[1 - 1] = 0;
在 k = 0 之后,str[i] = str[2] = b, str[k] = a 两者仍不相等。但因为此时的最长相等前后缀已经为0,没有可以再减少的了,所以vNext[i] = vNext[2] = 0;

3. 代码

void GetNext(const string& str, vector<int>& vNext)
    {
        vNext.resize(str.size());
        vNext[0] = 0;
        int i = 0;              // str[0 ~ i]表示当前正在求的子串
        int k = vNext[0];       // k表示的是str[0, i - 1]子串的最长相等前后缀的长度,所以str[k] 表示的是str[0, i - 1]子串最长相等前后缀的前缀的后一个字符

        while(i < str.size())
        {
            if(str[i] == str[k])    // 说明str[0, i]的最后一个元素与str[0, i - 1] 的第一个不相等的元素相等
            {
                k++;                // 则最长相等前后缀的长度加1
                vNext[i] = k;       // 也即是当前str[0, i]的最长相等前后缀长度
                i++;                // 求解下一个子串
            }
            else                    // 如果不相等,说明之前的最长相等前后缀需要缩短
            {   
                if(k == 0)          // 退无可退,则等于0
                {
                    vNext[i] = 0;
                    ++i;
                }
                else
                {
                    k = vNext[k - 1]; 
                }
            }
        }
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值