浅谈字符串匹配算法 —— BF算法、RK算法

概述

BF算法和RK算法都是单模式串匹配的算法。所谓单模式串匹配算法通俗来说就是一个串和一个串进行匹配

常见的单模式串匹配算法还有BM算法和KMP算法。

与之相对的就是多模式串匹配算法,就是在一个串中同时查找多个串,常见的有Trie 树和 AC 自动机。

 

两个概念

模式串和主串

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

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

 

BF算法

BF 是 Brute Force 的缩写,中文叫作暴力匹配算法,也叫朴素匹配算法。

这种算法的字符串匹配方式很“暴力”,逻辑比较简单、好懂,但相应的性能不高。

BF算法的思想

BF 算法的思想可以用一句话来概括:

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

这种算法和思想,若在极端情况下:若主串为 “aaaaaaaaaaaaa......”(省略无数个a),模式串为“aaaaab”。

我们每次都比对 m 个字符,要比对 n-m+1 次。

所以,这种算法的最坏情况时间复杂度是 O(n*m)。

BF算法的适用场景

尽管理论上,BF 算法的时间复杂度很高,是 O(n*m),但实际中它却是一个比较常用的字符串匹配算法。

主要原因有二:

  1. 大部分情况下,模式串和主串的长度都不会太长。而且每次模式串与主串中的子串匹配的时候,当中途遇到不能匹配的字符的时候,就可以就停止了,不需要把 m 个字符都比对一下。所以,尽管理论上的最坏情况时间复杂度是 O(n*m),但是,统计意义上,大部分情况下,算法执行效率要比这个高很多。
  2. BF算法思想简单,代码实现也非常简单。简单意味着不容易出错,如果有 bug 也容易暴露和修复。在工程中,在满足性能要求的前提下,简单是首选。这也是我们常说的KISS(Keep it Simple and Stupid)设计原则。

RK算法

RK 算法的全称叫 Rabin-Karp 算法,是由它的两位发明者 Rabin 和 Karp 的名字来命名的。

RK算法比较像BF算法的升级版。

BF算法是如果模式串长度为 m,主串长度为 n,那在主串中,就会有 n-m+1 个长度为 m 的子串。

我们只需要暴力地对比这 n-m+1 个子串与模式串,就可以找出主串与模式串匹配的子串。

但是每次检查主串与子串是否匹配,需要依次比对每个字符,所以 BF 算法的时间复杂度就比较高,是 O(n*m)。

对BF算法稍加改造,引入哈希算法,时间复杂度立刻就会降低。

RK算法的思想

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

如果某个子串的哈希值与模式串相等,那就说明对应的子串和模式串匹配了。

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

但是通过哈希算法计算子串的哈希值的时候,我们需要遍历子串中的每个字符。

尽管模式串与子串比较的效率提高了,但是,算法整体的效率并没有提高。

需要提高哈希算法计算子串哈希值的效率。

而且计算哈希值还存在着哈希冲突的问题。所以RK算法的核心还是在于哈希函数的设计。

哈希函数既要简单高效,又需要减少哈希冲突的概率。

假设字符串中只包含 a~z 这 26 个英文字母。比如将每一个字母从小到大对应一个素数,我们可以把字符串中每个字母对应的

数字相加,最后得到的和作为哈希值。这样冲突的概率就会降低一些。

当存在哈希冲突时,有可能会出现子串和模式串的哈希值虽然是相同的,但是两者本身并不匹配。

解决这种方法其实很简单,可以类比之前博文讲的对象比较问题(hashCode和equals)。

当我们发现一个子串的哈希值跟模式串的哈希值相等的时候,我们只需要再对比一下子串和模式串本身就好了。

如果子串的哈希值与模式串的哈希值不相等,那对应的子串和模式串肯定也是不匹配的,就不需要比对子串和模式串本身了。

RK算法的重点就是在哈希算法的设计上:

哈希算法的冲突概率要相对控制得低一些。如果存在大量冲突,就会导致 RK 算法的时间复杂度退化,效率下降。

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

但是一般情况下,哈希算法设计合理的话,冲突不会很多,RK 算法的效率还是比 BF 算法高的。

发布了113 篇原创文章 · 获赞 25 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/qq_42006733/article/details/105072014