• 数据结构与算法之美-字符串匹配(上)


    BF (Brute Force) 暴力/朴素匹配算法

    主串和模式串

    我们在字符串 A 中查找字符串 B,那字符串 A 就是主串,字符串 B 就是模式串。

    我们把主串的长度记作 n,模式串的长度记作 m。因为我们是在主串中查找模式串,所以 n>m。

    BF算法思想

    在主串中,检查起始位置分别是 0、1、2…n-m 且长度为 m 的 n-m+1 个子串,看有没有跟模式串匹配的。

    BF算法的缺点

    在极端情况下,如主串是“aaaaa…aaaaaa”,模式串是“aaaaab”。我们每次都比对 m 个字符,需要比对 n-m+1 次。这种算法的最坏情况时间复杂度是 O(n*m)。

    但在实际开发在BF是一种常用的字符串匹配算法。因为实际的软件开发中,大部分情况下,模式串和主串的长度都不会太长,算法执行效率要比 O(n*m)高很多。而且朴素字符串匹配算法思想简单,代码实现也非常简单。

    RK (Rabin-Karp) 算法

    它其实就是 BF 算法的升级版。对朴素的字符串匹配算法稍加改造,引入哈希算法,时间复杂度立刻就会降低。

    RK算法思想

    通过哈希算法对主串中的 n-m+1 个子串分别求哈希值,然后逐个与模式串的哈希值比较大小。

    如果某个子串的哈希值与模式串相等,那就说明对应的子串和模式串匹配了,这里先不考虑哈希冲突的问题。

    哈希值是一个数字,数字之间比较是否相等是非常快速的,所以模式串和子串比较的效率就提高了。

    巧妙的哈希算法

    通过哈希算法计算子串的哈希值的时候,需要遍历子串中的每个字符。尽管模式串与子串比较的效率提高了,但是算法整体的效率并没有提高。这就需要设计一个非常巧妙的哈希算法了。

    假设要匹配的字符串的字符集中只包含 K 个字符,我们可以用一个 K 进制数来表示一个子串,这个 K 进制数转化成十进制数,作为子串的哈希值。

    假设要处理的字符串只包含 a~z 这 26 个小写字母,那就用二十六进制来表示一个字符串。计算哈希值的时候,我们只需要把进位从 10 改成 26 就可以。

    这种哈希算法有一个特点,在主串中,相邻两个子串的哈希值的计算公式有一定关系。即可以使用 s[i-1] 的哈希值很快的计算出 s[i] 的哈希值。公式如下所示:

    //其中, h[i]、h[i-1] 分别对应 s[i] 和 s[i-1] 两个子串的哈希值
    h[i] = 26*(h[i-1]-26^(m-1)*(s[i-1]-'a')) + (s[i+m-1]-'a');

    其中 26^(m-1) 这部分的计算,可以通过查表的方法来提高效率。事先计算好 26^0、26^1等等,并且存储在一个长度为 m 的数组中。

    公式中的“次方”就对应数组的下标。需要计算 26 的 x 次方的时候,就可以从数组的下标为 x 的位置取值,省去了计算的时间。

    综上所述,可得RK 算法整体的时间复杂度是 O(n)。

    哈希算法的缺点

    模式串很长,相应的主串中的子串也会很长,通过上面的哈希算法计算得到的哈希值就可能很大,可能会超过了计算机中整型数据可以表示的范围。

    前面设计的哈希算法是没有散列冲突的。因此,为了能将哈希值落在整型数据范围内,是可以牺牲一下,允许哈希冲突的。比如将26进制转为10进制的算法改为数字相加。

    当存在哈希冲突的时候,有可能子串和模式串的哈希值虽然是相同的,但是两者本身并不匹配。我们只需要再对比一下子串和模式串本身就好了。

    如果存在大量冲突,就会导致 RK 算法的时间复杂度退化,效率下降。极端情况下,如果存在大量的冲突,每次都要再对比子串和模式串本身,那时间复杂度就会退化成 O(n*m)。

  • 相关阅读:
    GitHub 教程【转】
    Github 教程
    Excel 多个单元格输入同样内容
    寻找问题远比解决问题重要!
    蒲公英: 一个提供App 存储、分发、Bug管理的网站
    Dacapao 实验集(9.12 版本) 能不能给个网址?【内存分析实验】
    软件测试思维导图[ZZ]
    程序员、技术领导、管理者各有烦恼,你占了几条?ZZ
    r test
    mooctest项目总结 【转载】
  • 原文地址:https://www.cnblogs.com/errornull/p/10168298.html
Copyright © 2020-2023  润新知