哈希表的原理

本文介绍了哈希表的基本原理,包括其用于高效数据访问的性质,以及哈希冲突的概念。讨论了开放寻址法(如线性探测和双重哈希)和链表法作为解决冲突的策略,并提到了数组扩容在处理哈希冲突中的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Python微信订餐小程序课程视频

https://edu.youkuaiyun.com/course/detail/36074

Python实战量化交易理财系统

https://edu.youkuaiyun.com/course/detail/35475

哈希表的原理

简介

哈希表是一种根据关键字key来访问值value的一种数据结构。

哈希表的基本原理

哈希表的本质是数组哈希函数数组不难理解,那什么是哈希函数

在哈希表中,它的作用就是将哈希表的某个key作为输入,然后经过一系列的运算后,得到数组的某

image

个索引。一种很朴素的思路是,先用key计算出一个很大的数,然后对数组长度取模,从而得到索引,这只是众多方法中的一种,其他的比如:直接寻址法,平方取中法等。

得到索引后就可以通过索引对数组执行插入或查找的操作,因为本质上是通过索引来访问数组,所以哈希表的插入和查找的效率非常高,时间复杂度都是O(1)

哈希冲突

我们不难发现哈希函数是整个哈希表的关键。所以为了更好的性能,我们希望在尽可能短的时间内,相同的key经过哈希函数的计算,可以得到相同的索引,不同的key经过哈希函数的计算,可以得到不同的索引,但在实际中往往事与愿违,不同的key小概率会计算出相同的索引,这就是哈希冲突(collision),几乎所有的哈希函数都存在这个问题。

这里介绍几个常见的解决哈希冲突的方法:

开放寻址法

开放寻址是一种思想,如果通过哈希函数计算出的索引所对应的空间已经被占用了,就再找一个还没被占用的空间将数据存进去。

常见的体现开放寻址思想的方法:

  • 线性探测法:简单来说就是从当前被占用的空间的索引开始,向下遍历整个数组,直到找到空闲空间为止。如下图所示:

image

  • 双重哈希法:使用多个哈希函数来计算索引,如果第一个哈希函数计算得到的索引所对应的空间已被占用,就用第二个,第二个被占用就用第三个,以此类推,直到计数出没被占用的空间对应的索引。

链表法

链表法是一种更加常见的解决哈希冲突的方法,Java中的HashMap就是采用这种方法。在这种方法中,数组索引对应的空间并不直接存储数据,而是存储一个链表的地址,而数据存在链表中。如下图所示:

image

这样发生冲突时,就可将冲突的key对应的数据存在同一个链表上,当需要取数据时,就先找到key对应的链表,然后遍历链表。

数组扩容

上面说的方法只能在一定程度上解决哈希冲突,因为毕竟数组的容量有限,当频繁插入数据时,因为数组的容量有限,所以就会使哈希冲突加剧,进而使链表的长度增加,链表的长度增加,就会使得查找的性能降低,这不是我们想看到的结果,所以要对数组扩容。

image

那什么时候给数组扩容呢?装载因子(已插入元素的数量除以数组容量)超过某一阈值时就进行扩容,Java中HashMap的装载因子是0.75,当然,也可以是别的值。因为之前插入的元素都是按照原数组的长度来计算索引的,所以一旦数组扩容后,长度改变,就要重新进行计算,然后将已插入的元素移动到新的位置上,所以数组扩容不仅仅只是将容量增大而已。

### 哈希表工作原理详解 #### 什么是哈希表哈希表(Hash Table),也称为散列表,是一种基于键值对的数据结构。这种数据结构通过使用特定的函数——哈希函数,将给定的关键字映射到表中的一个位置来访问记录,使得关键字与其存储地址之间建立关联[^2]。 #### 如何构建哈希表? 为了创建一个有效的哈希表,需要定义两个主要组件: - **哈希函数**:用于计算输入项对应的索引位置。理想情况下,不同的键应产生不同且均匀分布的位置编号;然而实际上可能会发生冲突,即多个键被分配到了同一个桶里。 - **解决碰撞的方法**:当两个或更多个键产生了相同的哈希值时就会引发所谓的“碰撞”。常见的处理方式有开放寻址法、拉链法等。 #### 插入操作流程 假设现在要向哈希表中插入一个新的条目,则会经历如下过程: 1. 计算待存对象的 `hashCode()` 方法得到其原始哈希码; 2. 使用自定义或者默认提供的压缩方法调整此哈希码至合适的范围作为实际下标; 3. 如果目标槽位为空则直接放置新元素;否则执行相应的防撞策略直至找到合适的空间为止。 ```java public class SimpleHashMap<K, V> { private Entry[] table; static final int DEFAULT_CAPACITY = 16; public void put(K key, V value){ if (key == null) return; // 不允许null键 int hash = getHash(key); int index = Math.abs(hash % table.length); for(Entry entry : table[index]){ if(entry.getKey().equals(key)){ entry.setValue(value); // 更新已有键对应的新值 return; } } // 添加新的entry到指定index处 addNewEntry(index, new Entry<>(key,value)); } private int getHash(Object obj){ return Objects.hashCode(obj); } } ``` 上述代码展示了简化版的哈希表插入逻辑,其中包含了如何利用`hashCode()`获取初始哈希值并最终确定具体存放位置的过程。 #### 查找操作说明 对于查找而言,同样先依据相同的方式获得候选位置,之后遍历该处链接寻找匹配项即可完成整个检索动作。值得注意的是,在设计良好的哈希方案支持之下,平均时间复杂度接近O(1),这正是哈希表高效性的体现之一。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值