Rolling Hashes,超全解释
Rolling Hashes 是一种字符串匹配算法。它采用哈希函数的思想,将一个字符串映射成一个整数,并使用这个整数进行比较。
Rolling Hashes 算法具有如下特点:
- 时间复杂度为 O(n),其中 n 为字符串长度。
- 空间复杂度为 O(1)。
- 可以解决大部分字符串匹配问题,包括模式匹配、最长公共子串、最长回文子串等等。
本文将介绍 Rolling Hashes 的基本原理、应用场景、算法流程和优缺点等方面,希望对大家有所帮助。
基本原理
1. 哈希函数
哈希函数是将输入映射到固定大小输出的函数。它可以将一个字符串映射成一个整数,这个整数称为哈希值。
通常我们使用的哈希函数具有以下两个特点:
- 均匀性:对于任意两个不同的输入,哈希值相等的概率很小。
- 稳定性:对于同一个输入,哈希值相同。
常见的哈希函数包括:
- 直接哈希法:将字符串转化为整数。
- 幂次哈希法:将字符串看作 p 进制数,计算每一位的权值,最后求和。
- 旋转哈希法:将字符串看作一个循环序列,对于所有可能的循环位,计算哈希值,并取最小值。
2. 滚动哈希
滚动哈希是一种快速计算字符串哈希值的方法。它利用字符串前后子串的关系,只需要 O(1) 的时间就可以计算新的哈希值。
具体来说,滚动哈希的计算方法如下:
h a s h ( s i + 1... i + m ) = d ( h a s h ( s i . . . i + m − 1 ) − s i × d m − 1 ) + s i + m hash(s_{i+1...i+m})=d(hash(s_{i...i+m-1})-s_i \times d^{m-1})+s_{i+m} hash(si+1...i+m)=d(hash(si...i+m−1)−si×dm−1)+si+m
其中, h a s h ( s i . . . i + m − 1 ) hash(s_{i...i+m-1}) hash(si...i+m−1) 表示从第 i 个字符开始长为 m 的子串的哈希值,d 表示一个常数,称为进制数,通常取一个较大的质数,例如 31、131 或 13331 等等。 s i s_i si 和 s i + m s_{i+m} si+m 表示字符串中第 i 个字符和第 i+m 个字符。
这个式子的含义是:新的子串的哈希值等于旧的子串的哈希值乘以进制数再加上新的字符的哈希值。
滚动哈希的优劣性分析将在后面进行介绍。
应用场景
Rolling Hashes 算法广泛应用于字符串匹配问题。下面介绍 Rolling Hashes 在各个问题上的具体应用。
1. 字符串匹配
字符串匹配问题是指,给定一个文本串和一个模式串,判断模式串是否在文本串中出现过。例如,给定文本串 T T T 和模式串 P P P,判断 P P P 是否是 T T T 的子串。
Rolling Hashes 可以通过计算文本串和模式串的哈希值来解决字符串匹配问题。具体来说,我们可以先计算出模式串的哈希值,然后依次对文本串中的每个长度为模式串长度的子串计算哈希值,并将其与模式串的哈希值进行比较。如果匹配成功,则返回位置。
2. 最长公共子串
最长公共子串问题是指,给定两个字符串 s 1 s1 s1 和 s 2 s2 s2,找到它们的最长公共子串。例如,对于字符串 “abcde” 和 “ababcde”,它们的最长公共子串为 “abcde”。
Rolling Hashes 可以通过计算两个字符串的哈希值来解决最长公共子串问题。具体来说,我们可以先将两个字符串拼接起来,然后对于所有长度小于等于 n n n 的子串计算哈希值,其中 n n n 是字符串长度。最后,我们在两个字符串中分别查找哈希值相等的最长子串,即可找到它们的最长公共子串。
3. 最长回文子串
最长回文子串问题是指,给定一个字符串 s s s,找到它的最长回文子串。例如,对于字符串 “babad”,它的最长回文子串为 “bab” 或 “aba”。
Rolling Hashes 可以通过计算字符串反转后的哈希值来解决最长回文子串问题。具体来说,我们可以先将字符串反转后,与原字符串拼接起来,然后对于所有长度小于等于 n n n 的子串计算哈希值,其中 n n n 是字符串长度。最后,在这个字符串中查找哈希值相等的最长子串,即可找到原字符串的最长回文子串。
算法流程
Rolling Hashes 的算法流程如下:
- 计算模式串的哈希值。
- 对于文本串中每个长度为模式串长度的子串,计算其哈希值,并与模式串的哈希值进行比较。
- 如果匹配成功,则返回位置。
下面是 Rolling Hashes 的实现代码:
def rolling_hash(text, pattern):
n = len(text)
m = len(pattern)
d = 31
q = 10**9 + 7
# Calculate hash of pattern
hp = 0
for c in pattern:
hp = (hp * d + ord(c)) % q
# Calculate hash of first window of text
ht = 0
for c in text[:m]:
ht = (ht * d + ord(c)) % q
# Compare hash values
for i in range(n - m + 1):
if ht == hp:
if text[i:i + m] == pattern:
return i
if i < n - m:
ht = ((ht - ord(text[i]) * pow(d, m-1, q)) * d + ord(text[i+m])) % q
return -1
上述代码中,我们采用了取模运算的方法来避免哈希值溢出的问题。这个方法称为 “取模运算法则”,它可以保证在进行加减乘运算时,结果始终在 [ 0 , q ) [0,q) [0,q) 范围内。其中, q q q 是一个较大的质数。
优缺点
1. 优点
- 时间复杂度为 O(n),其中 n 为字符串长度。
- 空间复杂度为 O(1)。
- 可以解决大部分字符串匹配问题,包括模式匹配、最长公共子串、最长回文子串等等。
- 实现简单,易于理解。
2. 缺点
- 当哈希值相同时,需要进行字符串比较,时间复杂度较高。因此,滚动哈希的均匀性很重要。
总结
Rolling Hashes 是一种快速计算字符串哈希值的方法,它可以解决大部分字符串匹配问题,并且实现简单,易于理解。本文介绍了 Rolling Hashes 的基本原理、应用场景、算法流程和优缺点等方面,希望对大家有所帮助。