文章目录
HashTable 哈希/散列表
1. 基本思想
哈希表也叫散列表,用于存储key-value键值对,通过将key映射到表中的一个位置来以常数的时间实现插入、删除和查找的技术。
使用哈希/散列函数将要查找的键值转化为数组的索引,在理想状态下,不同的键对应不同的索引,如果每个键对应的索引都不同的话,其实就是直接用数组存储。但如果key的范围很大的话,需要的数组太大(比如处理海量数据时的bitHash),内存消耗上不划算。一般来说,为了折中,用小于键范围的数组(容量为TableSize)来存储。此时,不同的键就会对应相同的索引,这个时候,需要解决哈希冲突的问题。
2. 哈希函数
对于整数来说,最简单的哈希函数就是 key mod TableSize,这个时候,保证table的大小为素数时,该哈希函数能够对随机整数实现平均分配。
下面是一些在应用领域较为出名的哈希算法:
- MD5,信息摘要算法5,确保信息传输完整一致。MD5是输入不定长度信息,输出固定长度128bits的算法。
- SHA-1,用于HTTPS传输和软件签名。
- SHA-2:SHA-224/SHA-256/SHA-384/SHA-512并成为SHA-2
- SHA-3:之前命名为Keccak算法,是一个加密杂凑算法。
3. 解决哈希冲突的方法
3.1 分离链接法(拉链法)
拉链法可用于避免哈希冲突,它的思想是把hash相同的元素存放在链表中,将数组和链表结合起来,平衡时间和空间,实现快速的查找插入和删除。
即,将数组中的每个元素指向一个链表,链表中的每一个节点都存储索引相同的键值对。拉链法的数组长度要足够大,使得链表尽量小,以保证查找效率。
可以指定散列表的最大装载因子 load factor,一旦超过就会rehashing。
load factor = 散列表中的元素个数与散列数组大小的比值。
一般设定为1,即所有存储的元素个数大于数组大小时,就rehashing。
3.2 线性探测法
与拉链法不同的是,该方法在检测到哈希冲突时,不拉出一个链表进行存储,而是逐个探测找出后序的空单元来解决冲突,如下图所示,插入Sandra dee时,因为152已被John smith占据,故放置于153,同理,插入Ted Baker时,虽然hash值为153,但因为被占据,就顺序放到154。
在线性探测法中,如果hash函数不均衡,很容易出现聚集的区块,为了保证效率,装填因子一般设为0.5,此时插入平均需要2.5次探测,成功查找平均需要1.5次探测。
3.3 平方探测法
为了解决线性探测法中一次聚集问题的冲突解决办法。平方探测就是先探测后序的第1个,如果被占用,则探测后序的第 2 2 = 4 2^2=4 22=4个,如果还被占用,探测后序的第9个。
装填因子一般低于0.5,即确保散列表大小至少是表中元素的两倍大,这样平方探测解法总可以实现。
3.4 双散列
最后一个解决冲突的办法是双散列,在计算探测位置时应用第二个哈希函数。当键为字符串时,由于散列函数的计算比较耗时,通常使用简单易行的平方探测法。
4. 扩充再散列rehash
如果散列表中的元素太多,超过设置的装填因子,那么操作的运行时间开始过长,此时进行rehash,建立一个新表(一般是原表大小两倍后的第一个素数),使用新的哈希函数扫描原表,将元素复制到新表里。