Featured image of post 哈希

哈希

什么是哈希

当我们在要进行查找,但是元素的关键码与储存地址不是一一对应的话,我们的效率就较低(顺序查找O(n), 树查找(O(logn))),而哈希就是一个处理这类问题的一个算法。哈希通过函数把关键码“尽量均匀”地送到各个地址上,让我们在查找时通常能很快找到元素。至于“一一对应”?别想太多,冲突总会来,后面我们安排它。

原理

我们在储存元素的时候根据设计的函数去分配它们的地址,这样在查找时我们就可以根据元素关键码去找到对应的地址来得到元素。

e.g: hash(x) = x % capacity
-> hash(5) = 5 % 7 = 5 (we assume that capacity is 7)
-> hash(9) = 9 % 7 = 2

但是你可能很快就会发现“不行啊,那9放在2的位置,那我2放在哪,桥洞下吗?”。别急,这就叫哈希冲突——不同关键码撞在了同一个地址上,我们不会丢弃它,毕竟“2”也要有个家。

哈希冲突

哈希冲突就是多个元素的关键码对应的地址相同

避免(其实是减少)哈希冲突的方法

面对哈希冲突的时候我们没办法丢弃可怜的2,那么不要着急,我们有很多方法帮它安家(不会是漏风的桥洞下)。

重新设计函数

我们先想想为什么会出现冲突呢,首先可能我们的函数不太好,我们的“2”和太多人有同一个钥匙了,所以我们需要去重新设计一个函数,让拥有钥匙的人少一点(哈希表底层数组容量往往小于关键字的所以冲突在所难免),各位可以去查看常见的函数,自己来更具所需挑选,我们这里分享两个常见的:

直接定址法

公式:Hash(key) = a * key + b
特点:
·思路最直接,在键空间小且连续时可以做到“不冲突”。

·要求关键字分布基本连续、并且地址空间足够覆盖键空间。

缺点:如果关键字分布不连续,会造成大量空间浪费;一旦需要取模或压缩地址,仍可能产生冲突

除留余数法 - 最常用

公式:Hash(key) = key % p
特点:
·简单、高效,是实践中最常用的方法。

·关键在于模数 p 的选择。通常 p 应该是一个质数,并且不能太接近2的幂次方。

为什么p选质数? 因为质数能使得关键字对p取模后的结果,更均匀地分布在[0, p-1]的区间内,减少冲突。如果p是一个合数,那么关键字中与p含有公共因子的部分会导致某些槽位永远无法被映射到。

负载因子

负载因子 = 哈希表中已存储的元素数量 / 哈希表的总容量(槽位数)

它衡量了哈希表的 “满的程度”。负载因子越高,意味着哈希表越满,发生哈希冲突的概率就越大,从而导致插入、查找等操作的性能下降。

当负载因子超过某个 阈值 时,最直接有效的方法就是创建一个容量更大的新哈希表,然后将原哈希表中的所有键值对重新映射到新表中

解决哈希冲突的方法

巴菲特说过“只有通过斗争,我们才能认识自己;只有通过冲突,我们才能发现自己的力量。”,所以我们不能为了给可怜的“2”一个家就去避免冲突,我们要战斗为千千万万个“2”打出一片天地。那么我们来分享如何解决哈希冲突。

闭散列

我们这里分享两个冲突时寻找下一个地址的方法,不同方法的优缺点各有不同,各位具体情况具体分析

线性探测

探测序列:H(key), H(key)+1, H(key)+2, …, (H(key)+i) % TableSize

优点:实现简单,对CPU缓存友好(顺序访问内存)。

缺点:容易产生 初级聚集,即连续的位置被占用,形成长的冲突链,导致后续操作性能下降。

二次探测

探测序列:H(key), H(key)+1², H(key)+2², H(key)+3², …, (H(key)±i²) % TableSize

优点:缓解了初级聚集,让冲突的元素分布得更分散。

缺点:会产生 次级聚集(映射到同一初始地址的元素拥有相同的探测序列)。并且如果表长和负载控制不当,可能出现明明有空位却“兜兜转转找不到”的情况(通常选择表长为质数并控制负载因子可缓解)。

双重散列(相比前两个应该是最优的)

探测序列:(H1(key) + i * H2(key)) % TableSize

使用两个哈希函数。H1 确定初始位置,H2 确定探测的步长。

要求:H2(key) 不能为0,且必须与表大小 m 互质,以保证探测序列能覆盖整个表。

优点:是开放定址法中最好的方法之一,产生的探测序列最接近"随机",能有效减少各种聚集现象。

缺点:计算开销稍大。

开散列

这个大家更熟悉的名称应该是哈希桶,而且我感觉这个概念大家应该也很好理解怎么实现的

我们将相同地址的元素归于一个集合,也就是第一个桶,这个桶通过链表链接,那我们的哈希表就显而易见储存的是链表头的,这样我们查找元素的时候就只需要在一个更小的集合去查找,大大提高了效率而且也解决了冲突

使用 Hugo 构建
主题 StackJimmy 设计