美文网首页
【恋上数据结构与算法二】(十二)串(Sequence)

【恋上数据结构与算法二】(十二)串(Sequence)

作者: AlanGe | 来源:发表于2021-05-02 10:11 被阅读0次

    串(Sequence)

    ◼ 本课程研究的串是开发中非常熟悉的字符串,是由若干个字符组成的有限序列

    ◼字符串 thank 的前缀(prefix)、真前缀(proper prefix)、后缀(suffix)、真后缀(proper suffix)

    串匹配算法

    ◼ 本课程主要研究串的匹配问题,比如
    查找一个模式串(pattern)在文本串(text)中的位置

    ◼ 几个经典的串匹配算法
    蛮力(Brute Force)
    KMP
    Boyer-Moore
    Karp-Rabin
    Sunday

    ◼本课程用 tlen 代表文本串 text 的长度,plen 代表模式串 pattern 的长度

    蛮力(Brute Force)

    ◼ 以字符为单位,从左到右移动模式串,直到匹配成功

    ◼蛮力算法有 2 种常见实现思路

    蛮力1 – 执行过程

    蛮力1 – 实现

    蛮力1 – 优化

    ◼ 此前实现的蛮力算法,在恰当的时候可以提前退出,减少比较次数 pi=2

    ◼因此,ti 的退出条件可以从 ti < tlen 改为
    ti – pi <= tlen – plen
    ti – pi 是指每一轮比较中 text 首个比较字符的位置

    蛮力1 – 优化实现

    蛮力2 – 执行过程

    蛮力2 – 实现

    蛮力 – 性能分析

    ◼n 是文本串长度,m 是模式串长度

    ◼ 最好情况
    只需一轮比较就完全匹配成功,比较 m 次( m 是模式串的长度)
    时间复杂度为 O(m)

    ◼ 最坏情况(字符集越大,出现概率越低)
    执行了 n – m + 1 轮比较( n 是文本串的长度)
    每轮都比较至模式串的末字符后失败(m – 1 次成功,1 次失败)
    时间复杂度为 O(m ∗ (n − m + 1)),由于一般 m 远小于 n,所以为 O(mn)

    KMP

    ◼KMP 是 Knuth–Morris–Pratt 的简称(取名自3位发明人的名字),于1977年发布

    蛮力 vs KMP

    ◼ 对比蛮力算法,KMP的精妙之处:充分利用了此前比较过的内容,可以很聪明地跳过一些不必要的比较位置

    KMP – next表的使用

    ◼KMP 会预先根据模式串的内容生成一张 next 表(一般是个数组)

    KMP – 核心原理

    ◼当 d、e 失配时,如果希望 pattern 能够一次性向右移动一大段距离,然后直接比较 d、c 字符
    前提条件是 A 必须等于 B

    ◼所以 KMP 必须在失配字符 e 左边的子串中找出符合条件的 A、B,从而得知向右移动的距离

    ◼向右移动的距离:e左边子串的长度 – A的长度,等价于:e的索引 – c的索引

    ◼且 c的索引 == next[e的索引],所以向右移动的距离:e的索引 – next[e的索引]

    ◼总结
    如果在 pi 位置失配,向右移动的距离是 pi – next[pi],所以 next[pi] 越小,移动距离越大
    next[pi] 是 pi 左边子串的真前缀后缀的最大公共子串长度

    KMP – 真前缀后缀的最大公共子串长度

    KMP – 得到next表

    ◼将最大公共子串长度都向后移动 1 位,首字符设置为 负1,就得到了 next 表

    KMP – 负1的精妙之处

    ◼ 相当于在负1位置有个假想的通配字符(哨兵)
    匹配成功后 ti++、pi++

    KMP – 主算法实现

    KMP – 为什么是“最大“公共子串长度?

    ◼ 假设文本串是AAAAABCDEF,模式串是AAAAB

    ◼应该将1、2、3中的哪个值赋值给 pi 是正确的?

    ◼将 3 赋值给 pi
    向右移动了 1 个字符单位,最后成功匹配

    ◼将 1 赋值给 pi
    向右移动了 3 个字符单位,错过了成功匹配的机会

    ◼ 公共子串长度越小,向右移动的距离越大,越不安全

    ◼ 公共子串长度越大,向右移动的距离越小,越安全

    KMP – next表的构造思路

    ◼已知 next[i] == n

    1 如果 pattern.charAt(i) == pattern.charAt(n)
    那么 next[i + 1] == n + 1

    2 如果 pattern.charAt(i) != pattern.charAt(n)
    已知 next[n] == k
    如果 pattern.charAt(i) == pattern.charAt(k)
    ✓那么 next[i + 1] == k + 1
    如果 pattern.charAt(i) != pattern.charAt(k)
    ✓将 k 代入 n ,重复执行 2

    KMP – next表的代码实现

    KMP – next表的不足之处

    ◼假设文本串是 AAABAAAAB ,模式串是 AAAAB

    ◼ 在这种情况下,KMP显得比较笨拙

    KMP – next表的优化思路

    ◼已知:next[i] == n,next[n] == k

    ◼如果 pattern[i] != d,就让模式串滑动到 nexti位置跟 d 进行比较

    ◼如果 pattern[n] != d,就让模式串滑动到 nextn位置跟 d 进行比较

    ◼如果 pattern[i] == pattern[n],那么当 i 位置失配时,模式串最终必然会滑到 k 位置跟 d 进行比较
    所以 next[i] 直接存储 nextn即可

    KMP – next表的优化实现

    KMP – next表的优化效果

    KMP – 性能分析

    ◼KMP 主逻辑
    最好时间复杂度:O(m)
    最坏时间复杂度:O(n),不超过O(2n)

    ◼next 表的构造过程跟 KMP 主体逻辑类似
    时间复杂度:O(m)

    ◼KMP 整体
    最好时间复杂度:O(m)
    最坏时间复杂度:O(n + m)
    空间复杂度: O(m)

    蛮力 vs KMP

    ◼ 蛮力算法为何低效?

    ◼ 当字符失配时
    蛮力算法
    ✓ti 回溯到左边位置
    ✓pi 回溯到 0

    KMP 算法
    ✓ti 不必回溯
    ✓pi 不一定要回溯到 0

    相关文章

      网友评论

          本文标题:【恋上数据结构与算法二】(十二)串(Sequence)

          本文链接:https://www.haomeiwen.com/subject/gscnrltx.html