散列表简介

散列表

散列表也叫哈希表,百科中给的定义是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表

给定表M,存在函数f(key),对任意给定的关键字值key,代入函数后若能得到包含该关键字的记录在表中的地址,则称表M为哈希(Hash)表,函数f(key)为哈希(Hash) 函数。

键(key)

key是哈希表的关键码值,根据关键码值可以映射到哈希表中的记录

槽(slot/bucket)

哈希函数

哈希函数就是可以将给定的关键之key,计算出该key对应的在表中的地址的函数。
哈希函数有以下三个要去:

  1. 散列函数计算得到的散列值是一个非负整数
  2. 如果key1=key2,那么hash(key1)=hash(key2)
  3. 如果key1!=key2,那么hash(key1)!=hash(key2)

前两条很好实现,但是想要找到一个不同的key对应的散列值不一样的散列函数,几乎是不可能的,即便像业界著名的MD5、SHA、CRC等哈希算法,也无法完全避免这种散列冲突。

散列冲突

通常我们解决散列冲突用到两种方案,开放寻址法和链表法;

开放寻址法

开放寻址法的核心思想是,如果出现了散列冲突,我们就重新探测一个空闲位置,将其插入。我们说一个比较简单的探测方法,线性探测。

当我们往散列表中插入数据时,如果某个数据经过散列函数散列之后,存储位置已经被占用了,我们就从当前位置开始,依次往后查找,看是否有空闲位置,直到找到为止。

在这种情况下的查找元素的过程有点类似于插入过程。我们通过散列函数求出要查找元素的键值对应的散列值,然后比较数组中下标为散列值的元素和要查找的元素。如果相等,则说明就是我们要找的元素;否则就顺序往后依次查找。如果遍历到数组中的空闲位置,还没有找到,就说明要查找的元素并没有在散列表中。

在这种情况下,我们的删除算法要稍微有些特别,如果我们单纯的把要删除的元素设置为空,那么当我们查找的时候,如果找到的空闲位置是后来删除的,就会导致原来的查找算法失效,本来存在的数据,会被认定为不存在。

我们可以将删除的元素,特殊标记为deleted。当我们线性探测查找的时候,遇到标记为deleted的空间,继续往下探测。

线性探测其实存在很大的问题。当散列表中插入的数据越来越多时,散列冲突的可能性就会越来越大,空闲位置就会越少,线性探测的时间就会越久。极端情况下,我们可能需要探测整个散列表,所以最坏情况下的时间复杂度为O(n)。同理,在删除和查找时,也有可能会线性探测整张散列表,才能找到要查找或者删除的数据。

对于开放寻址冲突解决方法,除了线性探测方法外,还有另外两种比较经典的方法,二次探测双重散列

链表法

链表法是更加常用的散列冲突解决方法。在散列表中,每个“桶(bucket)”或者“槽(slot)”会对应一条链表,所有散列值相同的元素都放在相同槽位对应的链表中。

插入的时候,我们只需要通过散列函数计算出对应的散列槽位,将其插入到对应链表中即可,所以插入的时间复杂度为O(1)。当查找、删除一个元素时,我们同样通过散列函数计算出对应的槽,然后遍历链表查找或者删除。

装载因子

装载因子计算公式为:

散列表的转载因子 = 填入表中的元素个数/散列表的长度

装载因子越大,说明空闲位置越少,冲突越多,散列表的性能会下降

hash退化与拒绝服务攻击

hash退化是指当所有的key经过hash函数之后都映射到同一个槽中,hash表就会退化成链表。

当HashTable就会退化成链表,服务器有可能处理一次请求要花上十几分钟甚至几个小时的时间,一台PC机就可以搞定一台服务器,根本不用分布式攻击。如果Web应用框架采用的Hash机制存在漏洞。那么攻击者可以轻而易举的实施拒绝服务攻击。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值