(Rabin-Karp算法)匹配字符串(滚动哈希)

(Rabin-Karp算法)匹配字符串(滚动哈希)



Rabin-Karp算法的思路是将字符串的比较转换成数字的比较。比较两个长度为m的字符串是否相等需要O(m)的时间,而比较两个数字是否相等通常可以是O(1)。为了将字符串映射到对应的数字,故此需要用到哈希函数。


接下来的问题是,如何快速将字符串映射到对应的数字呢?如果需要在 O(L)的时间内算出编码,这种方法就没有意义了,因为这个直接进行字符串比较需要的时间相同。为了能够快速计算出字符串编码,我们可以将字符串看成一个 26 进制的数(因为字符串中仅包含26个字母),它对应的 10 进制的值就是字符串的编码值。

首先将字符转换为 26 进制中的 0-25,即通过 arr[i] = (int)S.charAt(i) - (int)‘a’,可以将字符串 abcd 转换为 [0, 1, 2, 3],它对应的 10 进制值为:
在这里插入图片描述
我们将这个表达式写得更通用一些,设 c[i]为字符串中第 i 个字符对应的数字,a = 26为字符串的进制,那么有:
(Rabin-Karp算法)匹配字符串(滚动哈希)_第1张图片



Rabin-Karp 算法用于多模式搜索,常用于重复检测和生物信息学中寻找两个或多个蛋白质的相似性。例如,判断一个字符串中是否有重复的字符串出现或者一个字符串中是否包含另一个字符串就可以用Rabin-Karp 算法。

例如:

判断s="abcdefghijkl"是否包含"bcde"子串?

利用上面公式先计算出"bcde"的哈希编码,然后开始在字符串s中找,首先将字符转换为 26 进制中的 0-25,即通过 arr[i] = (int)S.charAt(i) - (int)‘a’,可以将字符串 abcd 转换为 [0, 1, 2, 3],向右移动滑动窗口,从 abcd 变成 bcde,此时字符串对应的值从 [0, 1, 2, 3] 变成 [1, 2, 3, 4],移除了最高位的 0,并且在最低位添加了 4,那么我们可以快速地计算出新的字符串的编码:

在这里插入图片描述

更通用的写法是:

在这里插入图片描述

这样,我们只需要 O(L)的时间复杂度计算出最左侧子串的编码(即滑动窗口起始的编码),这个O(L) 和滑动窗口的循环是独立的。在滑动窗口向右滑动时,计算新的子串的编码的时间复杂度仅为 O(1)。


在实际的编码计算中,h0和h1的值可能会非常大,在 Java 语言中,会导致整数的上溢出。一般的解决方法时,对编码值进行取模,将编码控制在一定的范围内,防止溢出,即h = h % modulus。
h0对应的java代码为:

hash = ((hash * g) % MOD + arr[i]) % MOD;  //g可以理解为进制,本题中是26

h1对应的java代码为:

 hash = ((hash - arr[i - a] * Math.pow(g, a - 1) % MOD + MOD) % MOD * g % MOD + arr[i]) % MOD;  
 //a为滑动窗口的大小,需要匹配的字符串的长度



Rabin-Karp算法的优势

Rabin-Karp算法的优势是可以多维度或者多模式的匹配字符串。以多模式匹配为例,如果需要在文本T中找出模式集合P=[P1, P2, …Pk]中所有出现的模式。对于这个问题,Rabin-Karp算法的威力就能发挥出来了,Rabin-Karp算法能通过简单地扩展便能够支持多模式的匹配。

算法本质:按照k进制,进行编码,数据太多了,就取模。实际应用中因为无法确定起始字符串的长度,常常与二分一起使用。

看完之后大家可以用这个题练习一下:leetcode 1044. 最长重复子串[困难]

你可能感兴趣的:(算法,java,Rabin-Karp算法,算法,字符串哈希,滚动哈希)