哈希表

本文介绍了哈希表这一数据结构的特点及其实现方式。哈希表能够实现数据的快速查找、插入和删除,时间复杂度均为O(1)。文中详细解释了哈希函数的作用和设计方法,以及解决哈希冲突的链接地址法。

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

参考文章:http://en.wikipedia.org/wiki/Hash_function

来看这样一个例子。我们有这样一个电话簿:
人名 电话
Emily 010-789987
Smith 010-765443
...
这个表很大,我们希望有一种数据结构用于存储电话号码,并实现

1,当我们想找到一个人(key)对应的电话号码时,需要花费的时间是O(1).
2,希望删除一个人的电话时,需要的时间是O(1).
3,插入一个人的电话时,需要的时间是O(1).

也就是说这些操纵的时间都是常量,和数据结构里已经存了多少数据没有关系。


哈希表的特点
哈希表就是这样的一种数据结构。哈希表是用数组实现的,数组的特点是一旦建立就不可改变大小。
如果不需要有序遍历数据,并且可以提前预测数据量的大小,那么哈希表在速度和易用性方面是无与伦比的。
不论有多少数据,哈希表插入和删除只需要O(1)的时间。

哈希函数
前面的需求意味着:当我们拿到一个人名,也就是key,可以通过key的值直接计算出对应电话在数组中存储的位置(也就是数组元素的下标)。更直白一点:当我们拿到emily这个名字时,我们就能计算出电话存储在数组的,比如,第18个元素中。
因此我们需要一个函数,这个函数的输入是emily,输出是数组下标18(一个数字)。这个函数称为哈希函数。
当我们需要将数据插入哈希表时,使用哈希函数就知道应该插入到数组的什么地方去。
查找某个数据时,使用哈希函数就知道对应的数据在数组中的位置。
删除时使用哈希函数找到数据的位置,然后删除对应的数组元素。
因此实现哈希表,关键就是设计一个好的哈希函数,或称为哈希算法。

哈希函数的实现
y = f(x)
对应例子就是 f("emily") = 18
因此哈希函数需要解决的问关键题是:
1,不同的人名(key),应该尽可能的映射到不同的数组下标。如果一个以上的key映射到了同一个数组位置,称为冲突。
2,函数返回的数字,即数组下标不能过大。否则在实际应用中很难实现那么大的数组。
因此哈希表中存储的数据即不能过于集中(这样会大大增加冲突的几率),也不能过于分散(这样往往会导致数组过大,内存难以承受)。
一般来说,哈希表的容量就应该为数据大小的一倍。当容量过小时,往往哈希表会出现急剧的性能下降。

解决冲突的方法
Hash冲突是不可避免的。因此设计哈希算法时,必须考虑如何解决冲突。避免冲突的方法有很多,这里只介绍最简单的链接地址法。
将冲突的元素都放在一个数组元素里,只不过这个元素存的是包含所有冲突数据形成的一个链表,称为链接地址法。例如对于电话号码:

"Emily": 010-789987

"Smith": 010-765443

f("Emily") = 18,且 f("Tom") = 18,发生了hash碰撞。此时将Emily和Tom的号码放在一个链表里,且存储在A(18)中。但是如何知道同一链表中的哪个号码才是Emily的呢?因此实际链表中存储的不仅仅是号码(value),同时也包含人名(key)。实际上在数组和链表中存储都不仅仅只是value,而且将key也存储进去。

解决哈希值过大的方法

常用方法就是将hash值对数组长度取模,这样就保证数组下标永远不会超过数组长度。问题是可能会导致hash碰撞加剧。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值