漫画算法-学习笔记(06)

漫画算法-小灰的算法之旅(06)

1. 什么是散列表

散列表也称哈希表(hash table),这种数据结构提供了**键(key)值(value)**的映射关系。只要给出一个key,就可以高效查找到它所匹配的value,时间复杂度接近于O(1)。

2. 哈希函数

散列表是如何根据Key来快速找到它所匹配的value呢?

散列表在本质上是一个数组,因此可以进行元素的随机访问。数组的随机访问元素只能通过,a[0]、a[2]等方式访问,而散列表的key则是以字符串类型为主访问的。因此我们期望通过某种方式,把key和数组下标进行转换。而这个负责中转的过程则被称之为哈希函数

哈希函数是怎么实现的?

以java的常用集合HashMap为例,来看一看哈希函数在java中的实现。

在java中,每一个对象都有属于自己的hashcode,这个hashcode是区分不同对象的重要标识。无论对象自身的类型是什么,它们对应的ha shcode都是一个整型变量。

既然都是整型变量,想要转化成数组的下标也就不难实现了。最简单的转化方式是什么呢?是按照数组长度进行取模运算。

index =HashCode(Key)%(Array.length)

而实际上,JDK中的哈希函数为了提高性能,并没有直接采用取模运算,而是采用了位运算的方式。

3. 散列表的读写操作

有了哈希函数,就可以在散列表中进行读写操作了。

写操作(put)

写操作就是在散列表中插入新的键值对(entry)

具体步骤:

  1. 通过哈希函数,把key转化为数组下标n
  2. 如果数组下标n对应位置没有元素,就把键值对Entry填充到数组下标n到位置上。但由于数组的长度是有限的,当插入的entry越来越多是,不同的key通过哈希函数获得下标有可能是相同的,这种情况就叫做哈希冲突。

哈希冲突是无法避免的,既然不能避免,我们就要想法解决才对。解决哈希冲突的方法主要有两种:一种叫开放寻址法,一种叫链表法。

开放寻址法:当一个key通过哈希函数获得对应的数组下标已经被占用时,我们可以“另谋高就”,寻找下一个空档位置。可以采用寻找当前元素的最后一个元素,来存储entry。

链表法:以java中hashMap为例,HashMap数组的每一个元素不仅是一个Entry对象,还是一个链表的头节点。每一个Entry对象通过Next指针指向它的下一个Entry节点。当新来的Entry映射到与之冲突的数组位置时,只需要插入到对应的链表中即可。

读操作(get)

读操作就是通过给定的Key,在散列表中查找对应的Value.

具体步骤:

  1. 通过哈希函数,把Key转化为数组下标N。
  2. 找到数组下标N所对应的元素,如果这个元素的Key是指定的key值,则找到该key的value值。如果不是,则遍历该数组下标N对应的链表,寻址查找,看是否能找到目标Key值相匹配的Entry节点。
扩容(resize)
什么时候需要进行扩容?

当经过多次元素插入,散列表达到一定饱和度时,Key映射位置发生冲突的概率会逐渐提高。这样以来,大量元素拥挤在相同数组下标N位置上,形成很长的链表,对后续插入操作和查询操作的性能都有很大影响。此时,散列表就需要扩展它的长度和容量了,也就是扩容

对于Java中的HashMap来说。影响其扩容的因素有两个。
Capacity: 即HashMap的当前长度

LoadFactor: HashMap的负载因子,默认值为0.75f

衡量HashMap是否需要扩容的条件时:HashMap.Size >=Capacity x LoadFactor

散列表的扩容操作,具体做了什么事情?
  1. 扩容,创建一个新的Entry空数组,长度是原数组的2倍。
  2. 重新Hash,遍历元Entry数组,把所有的Entry重新Hash到新数组中。为什么要重新Hash呢?因为长度扩大之后,Hash的规则也随之改变。经过扩容,原本拥挤的散列表重新变得稀疏,原有的Entry也重新得到了尽可能均匀的分配。

4. 总结

散列表可以说是数组和链表的结合,它在算法中的应用很普遍,是一种非常重要的数据结构。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值