算法 - 字符串算法 - 字符串哈希算法介绍
介绍字符串中的哈希算法。
假设有 个长度为 的字符串,问其中最多有几个字符串是相等的。
直接比较两个长度为 的字符串是否相等的时间复杂度是 的。因此需要枚举 对字符串进行比较,时间复杂度为 。如果我们把每个字符串都用一个哈希函数映射成一个整数。问题就变成了查找一个序列中的众数,时间复杂度变为了 。
字符串哈希函数
注:本小节代码仅为演示,请不要实际运行。代码模板在下小节的代码实现中介绍。
一个设计良好的的字符串哈希函数可以让我们先用 的时间复杂度预处理,之后每次获取这个字符串的一个子串的哈希值都只要 的时间。这里重点介绍 BKDRHash。
BKDRHash 的基本思想就是把一个字符串当作一个 进制数来处理。
代码如下:
const int HASH_K = 131;
const int HASH_M = 1e9 + 7;
int BKDRHash(char* str)
{
int ans = 0;
for (int i = 0; str[i]; ++i) {
ans = (ans * HASH_K + str[i]) % HASH_M;
}
return ans;
}
例如,处理字符串 abac
。
第一个循环,ans
为 。
第二个循环,ans
为 。
第三个循环,ans
为 。
第四个循环,ans
为 。
其中,模运算是为了保证运算过程中数的范围,防止指数爆炸。且 HASH_K
和 HASH_M
最好取成质数,这样可以减少哈希冲突。
现在我们考虑子串的哈希,假设字符串 的下标从 开始,长度为 ,我们得到 的 BKDRHash 值 ha[i]
。令哈希函数为 ,则有定义
计算代码如下:
const int HASH_K = 131;
const int HASH_M = 1e9 + 7;
p[0] = 1;
ha[0] = 0;
for (int i = 1; i <= n; ++i) {
p[i] = p[i - 1] * HASH_K;
ha[i] = (ha[i - 1] * HASH_K + s[i]) % HASH_M;
}
现在询问 的 BKDRHash 可以得到(加的括号仅是为了方便展示后面得到的关系)
又注意到
而我们要求的 的哈希值为
可以发现得到如下关系式
因此我们预处理出 ha
数组和 的幂次 p
数组,之后每次询问 的哈希值,只要 的时间。
哈希函数代码实现
每次迭代后取模
如果我们按照我们上面推导的公式表述的式子做代码实现,会带来一个问题:指数爆炸。即 的幂次可能很高,以致超出了 C 语言的数据表示范围。因此我们需要每迭代一次,就对结果取一次模,就像上节的代码展示的那样,但这需要重新计算关系式 。
每一步的迭代表示为
注意到上式参与计算的数都大于等于 ,因此计算机上的 与数学上的 一致,此时 对任意 有性质
上述性质可参见相关的数学教材即可,或者自己尝试证明也行,并不困难。由式 和式 可将我们要计算哈希值的表达式进行化简
可将 和 看作上式的特例,因此我们得到了 和 的结果,此时你应该会发现这些结果与 非常相似,你也许会认为有下式成立
但是并不正确,因为式 中的 和 是由式 迭代产生的,两者相减可能为负,而被除数为负数,除数为正数时,产生的余数会不一致(数学上与 C 语言上的模运算,见链接)。我们只需将式 调整为
式 就是最终得到的关系式,利用该关系式,仍然可在 时间内计算子串的哈希值。
数据溢出与数据表示范围
在我们循环过程中,我们计算如下语句
ha[i] = (ha[i - 1] * HASH_K + s[i]) % HASH_M;
会出现的一种情况是,在计算 ha[i - 1] * HASH_K + s[i]
过程中,数据过大导致数据溢出,相当于对计算结果做了一次模运算。如果你构造哈希函数时就用的是数据的自然溢出来作为模运算,那计算过程中导致的溢出并不影响最终结果;但若不是如此,很可能导致哈希值计算不正确。
因此一般要开数据范围在 long long
,且 HASH_K
和 HASH_M
值要保证不发生溢出。
举一个例子,取 HASH_K
为 131
,取 HASH_M
为 1e9+7
,由于每次迭代后都取模,因此保证了 ha[i - 1]
不超过 1e9+7
,即使之后每次运算都尽量取到最大,也能保证计算过程 ha[i - 1] * HASH_K + s[i]
未溢出(都在 long long
的数据范围内)。
双哈希减小冲突
在计算一个字符串的哈希值的过程中,用两个不同的 和两个不同的模数 分别运算,将这两个结果用一个二元组表示,作为哈希的结果。
即每一步迭代产生两个哈希
此时将二元组作为哈希的结果,即
给定一个子串,计算其哈希,就是对该子串按式 分别做两次哈希,然后将这两个哈希值组合为二元组的结果。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!