一、算法概述
- 定义:字符串哈希是一种将字符串转换为数值(哈希值)的技术,通过哈希值快速比较字符串的相等性或查找子串。其核心是利用基数映射和前缀哈希数组,实现子串哈希值的O(1)时间查询。
- 核心思想:将字符串视为基数为B的数,通过预处理前缀哈希数组和幂次数组,将子串的哈希计算转化为数值运算,从而高效判断子串是否相同。
- 应用场景:
- 字符串匹配(如KMP、Boyer-Moore算法的优化)
- 重复子串检测
- 字符串哈希表构建
二、算法思路
- 基数与幂次:
- 选择一个基数B(如26、271、1e9+7等),将字符映射为数值(如a=1,b=2,…)。
- 预处理幂次数组
Power[i] = B^i,用于快速计算哈希值的权重。
- 前缀哈希数组:
- 定义
Hash[i]为字符串前i个字符的哈希值,计算公式为:
Hash[i] = Hash[i-1] * B + s[i]
其中s[i]为第i个字符的数值映射。
- 定义
- 子串哈希计算:
- 子串
s[l...r]的哈希值为:
get(l, r) = Hash[r] - Hash[l-1] * Power[r-l+1]
该公式通过前缀哈希和幂次抵消前l-1个字符的影响,得到子串的独立哈希值。
- 子串
三、伪代码实现
1. 数据结构与全局变量
常量 maxn = 1000010 # 最大字符串长度
类型 ull = 无符号长整型 # 用于存储哈希值,避免溢出
常量 B = 271 # 哈希基数
数组 Power[maxn] # 幂次数组,Power[i] = B^i
数组 Hash[maxn] # 前缀哈希数组
2. 核心函数框架
算法:字符串哈希模板
输入:字符串 s
输出:支持快速查询子串哈希值的函数
函数 init(s):
# 初始化前缀哈希数组和幂次数组
Power[0] = 1 # B^0 = 1
Hash[0] = s[0]的数值映射 # 第一个字符的哈希值
for i from 1 to s.length()-1:
Hash[i] = Hash[i-1] * B + s[i]的数值映射 # 前缀哈希计算
Power[i] = Power[i-1] * B # 幂次计算
函数 get(l, r):
# 计算子串s[l...r]的哈希值(l, r从0开始索引)
if l == 0:
return Hash[r] # 从字符串开头开始的子串
return Hash[r] - Hash[l-1] * Power[r - l + 1] # 子串哈希公式
# 主程序(示例)
输入字符串 s
init(s)
输入查询次数 q
for each query:
输入 l, r
输出 get(l, r)
四、算法解释
1. 初始化过程
- 幂次数组:
Power[i]存储基数B的i次幂,用于后续计算哈希值时的权重调整。例如,Power[3] = B³表示第三位字符的权重。 - 前缀哈希数组:
Hash[i]表示前i+1个字符的哈希值(假设索引从0开始)。例如,字符串"abc"的Hash[0] = 'a',Hash[1] = 'a'*B + 'b',Hash[2] = ('a'*B + 'b')*B + 'c' = 'a'*B² + 'b'*B + 'c'。
2. 子串哈希计算原理
- 对于子串
s[l...r],其哈希值等价于完整哈希值Hash[r]减去前l个字符的哈希值Hash[l-1]乘以权重Power[r-l+1]。 - 数学推导:
假设字符串为s[0...n-1],则:
Hash[r] = s[0]*B^r + s[1]*B^(r-1) + ... + s[r]
Hash[l-1] = s[0]*B^(l-1) + s[1]*B^(l-2) + ... + s[l-1]
Hash[l-1] * Power[r-l+1] = s[0]*B^r + s[1]*B^(r-1) + ... + s[l-1]*B^(r-l+1)
因此,Hash[r] - Hash[l-1] * Power[r-l+1] = s[l]*B^(r-l) + ... + s[r],即子串s[l...r]的哈希值。
3. 示例演示
- 字符串:“abc”,基数B=10,字符映射为a=1, b=2, c=3
- 初始化:
Power[0]=1, Power[1]=10, Power[2]=100
Hash[0]=1, Hash[1]=1*10+2=12, Hash[2]=12*10+3=123 - 计算子串"bc"(l=1, r=2)的哈希值:
get(1, 2) = Hash[2] - Hash[0] * Power[2-1+1] = 123 - 1 * 100 = 23,对应数值"23",即子串"bc"的哈希值。
4. 哈希冲突处理
- 双哈希策略:使用两个不同的基数(如B1=271,B2=911382629)和模数(如1e18+3,1e18+7)计算双重哈希值,降低冲突概率。
- 模数选择:选择大质数(如1e9+7、1e18+3)作为模数,减少哈希碰撞的可能性。
五、复杂度分析
- 时间复杂度:
- 初始化:O(n),预处理前缀哈希数组和幂次数组。
- 单次查询:O(1),直接通过公式计算子串哈希值。
- 总时间复杂度:O(n + q),其中q为查询次数。
- 空间复杂度:
- 前缀哈希数组
Hash:O(n)。 - 幂次数组
Power:O(n)。 - 总空间复杂度:O(n)。
- 前缀哈希数组
- 优化点:
- 预处理幂次数组时可预先计算到最大可能长度,避免重复计算。
- 对于长字符串,使用unsigned long long自然溢出(模2^64)代替显式模数,减少计算开销。
- 适用范围:
- 适用于需要频繁查询子串相等性的场景,如字符串匹配、重复子串检测等,时间效率优于直接比较字符串。
本文为作者(英雄哪里出来)在抖音的独家课程《英雄C++入门到精通》、《英雄C语言入门到精通》、《英雄Python入门到精通》三个课程的配套文字讲解,如需了解算法视频课程,请移步 作者本人 的抖音直播间。
550

被折叠的 条评论
为什么被折叠?



