数据结构与算法之美(笔记17)字符串匹配:BF、RK算法

BF算法

先讲讲主串和模式串。

比方说,我们在字符串A中查找字符串B,那字符串A就是主串,字符串B就是模式串。我们把主串的长度记为n,模式串的长度记为m。因为我们是在主串中查找模式串,所以有n>m。

BF算法可以用一句话来概括:我们在主串中,检查其实位置分别为0、1、2...n-m 且长度为n-m+1个子串,看有没有跟模式串匹配的。

从上面的例子,我们可以看到,在极端的情况下,比如主串是"aaaa......aaaaaaa",模式串是"aaaaab" 。我们每一次都要对比m个字符,要比对n-m+1次,所以,这种算法的最坏时间复杂度就是O(n*m)。

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

  • 实际的软件开发中,大部分情况下,模式串和主串的长度都不会很长,而且每次模式串与主串中的子串匹配的时候,当中遇到不能匹配的字符的时候,就可以停止了,不需要把m个字符都比对一下,所以,尽管理论上最坏时间复杂度是O(n*m),但是,大部分情况下,这个算法执行效率都比较这个高。
  • 朴素字符串匹配算法思想简单,代码实现也比较简单,简单就意味这不容易出错。

RK算法

上面的BF算法我们知道,如果模式串长度为m,主串长度为n,那在主串中,就会有n-m+1个长度为m的子串,我们只要暴力比对这个n-m+1个子串与模式串,就可以找出主串和模式串匹配的子串。

但是BF算法需要依次比对每个字符,所以说BF算法的时间复杂度 就很高。

RK算法的思路是:我们通过哈希算法对主串中的n-m+1个子串分别求哈希值,然后逐个与模式串的哈希值比较大小。如果某个子串的哈希值与模式串相等,那就说明对应的子串和模式串匹配了。因为哈希值是一个数字,数字之间比较是否相等是非常快速的。

不过,通过哈希算法计算子串的哈希值的时候,需要遍历子串中的每个字符。尽管模式串与子串比较的效率提高了,但是,算法效率并没有提高。有没有方法可以提高哈希算法计算子串哈希值的效率呢?

这就需要哈希算法设计的非常有技巧了。我们假设要匹配的字符串中的字符集只包含k个字符,我们可以用一个K进制数来表示一个子串,这个K进制数转化为十进制数,作为子串的哈希值。

比如要处理的字符串中只包含了a~z这26个小写字母,那我们就用二十六进制来表示一个字符串。我们把a~z这26个数字,a就表示0,b就表示1,以此类推,z表示25。

在十进制的表示法中,一个数字的值是通过下面的方式计算出来的。对应到二十六进制,一个包含a到z这26个字符的字符串,计算哈希的时候,只需要把10改成26即可。

 

这种哈希算法有一个特点,在主串中,相邻两个子串的哈希值的计算公式有一定关系。 

在这里,我们很容易就能得出这样的规律:相邻两个子串s[i-1] 和 s[i](i 表示子串在主串中的起始位置,子串的长度都为 m),对应的哈希值计算公式有交集,也就是说,我们可以使用s[i-1] 的哈希值很快计算出s[i] 的哈希值。

 

这里有个小细节,那就是26^(m-1)这部分的计算,我们可以通过查表的方法来提高效率。我们事先计算好26^0,26^1,26^2......26^(m-1),并且储存在一个长度为m的数组中,公式中的“次方”就对应数组的下标。当我们需要计算26的x次方的时候,就可以从数组的下标为x的位置取值,直接使用,省去计算时间。

 

那么RK的时间复杂度是多少呢?

整个RK算法包括计算子串哈希值和模式串哈希值与子串哈希值的比较。第一部分,可以通过设计特殊的哈希算法,只需要扫描一遍主串就能计算出所有子串的哈希值了,这部分的时间复杂度是O(n) 。

模式串与子串哈希值之间的比较时间复杂度是O(1),总共需要比较n-m+1个字符串的哈希值,所以这部分的时间复杂度也是O(n),所以整体RK算法的时间复杂度就是O(n)。

这里还有一个问题,如果模式串很长,相应的主串中的子串也会很长,通过上面的哈希算法计算得到的哈希值就可能很大,如果超过了计算机中整型数据可以表示的范围,那该如何解决?

刚刚设计的哈希算法是没有散列冲突的,也就是说,一个字符串与一个二十六进制数一一对应,不同的字符串的哈希值肯定是不一样的。因为我们是基于进制来表示一个字符串的。实际上,我们为了能将哈希值落在整型数据范围内,可以牺牲一下,允许哈希冲突。

我们可以改变一下哈希算法,假设字符串中只包含a~z这26个英文字母,那我们每个字母对应一个数组,比如a对应1,b对应2,以此类推。我们可以把字符串中每个字母对应的数字相加,最后得到的和作为哈希值。但是这种哈希算法冲突的概率很高,那假设现在有了哈希冲突,我们应该如何解决?

实际上,我们发现一个子串的哈希值个模式串的哈希值相等的时候,我们只需要再对比一下子串跟模式串本身就好了。当然,如果子串的哈希值和模式串的哈希值不相等,那对应的子串和模式串肯定也是不匹配的,就不需要比对子串和模式串本身了。

猜你喜欢

转载自blog.csdn.net/weixin_42073553/article/details/88907343