4.2KMP算法

KMP算法

KMP算法是用来解决字符串匹配的问题。

*如:求文本串aabaabaaf中是否出现过模式串aabaaf。设遍历文本串时间复杂度为O(n),遍历模式串时间复杂度为O(m)。*

  • 显然,暴力法求解是两层for循环,时间复杂度为O(m × n)
  • 下面介绍的KMP算法时间复杂度将会大大提高,为O(m + n)

1.前缀表

①前缀和后缀

  • 前缀:包含首字母,不包含尾字母的所有子串。
  • 后缀:包含尾字母,不包含首字母的所有字串。

如:aabaaf

前缀包括:a、 aa、 aab、aaba、 aabaa五个子串。

后缀包括:f、 af、 aaf、 baaf、 abaaf五个子串。

②最长相等前后缀

如:

字符串a 最长相等前后缀为0

字符串aa 最长相等前后缀为1

字符串aab 最长相等前后缀为0

字符串aaba 最长相等前后缀为1

字符串aabaa 最长相等前后缀为2

字符串aabaaf 最长相等前后缀为0

③前缀表

前缀表就是记录字符串下标i之前(包括i)的字符串中,有多大长度的相同前缀后缀。

前缀表是用来回退的,它记录了模式串与主串不匹配的时候,模式串应该从哪里开始重新匹配。 即,当某个字符匹配失败时,前缀表告诉你下一步匹配时,模式串应该跳到哪个位置。

例:{0, 1, 0, 1, 2, 0}就是字符串aabaaf的前缀表。

④使用前缀表匹配的过程

如:文本串为aabaacaabaaf, 模式串为aabaaf。

  1. 文本串的c和模式串的f字符出现不匹配。我们要寻找模式串不匹配位置前面的子串的最长相等前后缀是2,模式串重新回到下标为2的位置开始匹配(2代表相等前后缀的长度,我们要跳到前缀的后面位置开始重新与文本串的字符进行匹配,而这个位置的下标正好等于前缀的长度)。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qPRHNu38-1644843052680)(C:\Users\ThinkStation K\AppData\Roaming\Typora\typora-user-images\1644809442093.png)]

  2. 文本串c和模式串的b字符也出现不匹配。同上,寻找模式串不匹配位置前面的子串的最长相等前后缀是1,模式串重新回到下标为1的位置开始匹配。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jS7XyPUG-1644843052681)(C:\Users\ThinkStation K\AppData\Roaming\Typora\typora-user-images\1644809772345.png)]

  3. 文本串c和模式串的字符a也出现不匹配。寻找模式串不匹配位置前面的子串的最长相等前后缀是0,即从头开始匹配

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Nbz21VyO-1644843052681)(C:\Users\ThinkStation K\AppData\Roaming\Typora\typora-user-images\1644809957763.png)]

  4. 文本串c和模式串的首字符a也不匹配,文本串继续后移,找到了匹配的字符。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-seZzi0ht-1644843052681)(C:\Users\ThinkStation K\AppData\Roaming\Typora\typora-user-images\1644810236430.png)]

2.代码实现

①next数组

next数组是前缀表的代码实现不涉及KMP的原理,next数组一般有2种表示方法:

以文本串aabaabaaf,模式串aabaaf为例。

  • 前缀表作为next数组。 当遇见冲突时,返回到冲突前一位前缀表对应的值所对应的下标。

aabaaf的next数组为:[0 1 0 1 2 0],模式串最后一个元素f匹配失败,则模式串返回f前一位前缀表所对应的值即2所对应的下标,重新开始匹配。

  • 前缀表整体右移一位,第一个元素赋值为-1。 遇见冲突时,直接返回next数组值对应的小标。

aabaaf的next数组为:[-1 0 1 0 1 2],模式串最后一个元素f匹配失败,模式串返回f所在next值即2所对应的下标,重新开始匹配。

②next数组实现代码(前缀表直接作为next数组)

求next数组的核心思想是——模式串与自身做匹配。 我们定义指针i和j。

  • 指针i:指向后缀末尾位置
  • 指针j:指向前缀末尾位置,也代表i之前的子串的最长相等前后缀长度。

求next数组的代码如下:

void getNext(int *next, const string &s) {
    int j = 0;
    next[0] = 0;
    for (int i = 1; i < s.size(); ++i) {
        //前缀末尾和后缀末尾不匹配的情况
        while (j > 0 && s[i] != s[j]) {
            j = next[j - 1];
        }
        if (s[i] == s[j]) {
            ++j;
        }
        next[i] = j;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值