字符串模式匹配问题——RK算法

RK算法

其他相关算法:
BM算法
KMP算法
RK 算法的全称叫 Rabin-Karp 算法,它是针对暴力求解BF方法引入了哈希算法的优化方法。在BF算法中,如果模式串长度为m,主串长度为n,对主串中n-m+1个长度为m的子串一个一个与模式串匹配,我们需要依次对比每个字符,因此时间复杂度为O(n*m)

RK算法思路:

通过哈希算法对主串中的 n-m+1 个子串分别求哈希值,然后逐个与模式串的哈希值比较大小。如果我们暂时不考虑哈希冲突并且某个子串的哈希值与模式串相等,那就说明对应的子串和模式串匹配

如果我们没有一个很好的计算哈希值的方法,那么针对每个字串计算哈希值,依然需要遍历每个字串,但这样算法的效率将并没有显著提升。因此,通过哈希算法计算哈希值就很重要了,影响着整个模式匹配的效率。

计算哈希值

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

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

我们知道,一个16进制表示成10进制的方法:

例:2AF5换算成10进制: 5 * 16^0 + F * 16^1 + A * 16^2 + 2 * 16^3 = 10997

那么我们可以从此延申,假如我们要求“cba"的哈希值:

*“cba"转换为10进制为:0 26^0 + 1 * 26^1 + 2 * 16^2 = 1351

怎么样?很简单吧?看到这里你可能就想到,万一计算过程溢出了怎么办?我们会在后面说

到这里,如果比较细心你可能就发现了相邻两个字串的求解哈希值中有一部分是有关系的,只是这一部分多乘或者少乘了个26

同时,我们可以借助查表得方法来尽可能避免大量的运算,将事先计算好 260、261、262……26(m-1),并且存储在一个长度为 m 的数组中,公式中的**“次方”就对应数组的下标**。

此时,我们只需要遍历依次主串,即可在O(n)时间复杂度范围内求得各个字串的哈希值。

通过此种方法,我们还可以解决二维上的模式匹配,比如假设有一个二维字符串矩阵,在其中查找另一个二维字符串矩阵。

相关问题

溢出与散列冲突

上述求取哈希值的过程可能会出现溢出,但这种方式的哈希算法是没有散列冲突的,每个字符串都有一个固定的唯一的哈希值。在实际情况中,我们是可以允许散列冲突的,当出现了主串子串与模式串哈希值相同的情况时,我们只需要再比较一下两个字符串判断到底是否是遇到散列冲突了。

哈希算法的设计方法有很多,假设字符串中只包含 a~z 这 26 个英文字母,那我们每个字母对应一个数字,比如 a 对应 1,b 对应 2,以此类推,z 对应 26。我们可以把字符串中每个字母对应的数字相加,最后得到的和作为哈希值。这种哈希算法产生的哈希值的数据范围就相对要小很多了,但同时,散列冲突的可能性就变大了。

当然,还有很多更加优化的方法,比如将每一个字母从小到大对应一个素数,而不是 1,2,3……这样的自然数,这样冲突的概率就会降低一些。

所以,哈希算法的冲突概率要相对控制得低一些,如果存在大量冲突,就会导致 RK 算法的时间复杂度退化,效率下降。极端情况下,如果存在大量的冲突,每次都要再对比子串和模式串本身,那时间复杂度就会退化成 O(n*m)。但也不要太悲观,一般情况下,冲突不会很多,RK 算法的效率还是比 BF 算法高的。

你可能感兴趣的:(数据结构与算法,专题,算法设计与应用,算法,数据结构,字符串)