前面已经连载完数组、链表、栈、队列以及各自在面试中的考察点,今天来学习哈希表;
哈希表
我对哈希表的理解,本质上是键值对构成的集合;最大的特点是根据key查找value的时间复杂度为1。
原因是:从存储结构上来讲,充分利用了数组按照位置查找,时间复杂度为O(1)的特点;
键值对的位置是通过hash函数对key求哈希值来确定的;
哈希函数
所有的函数都是完成输入到输出的映射,哈希函数完成的是由key到位置的映射;
即输入时key,输出时其在数组的位置;
设计哈希函数时应注意 以下几点:
- 函数计算不能过于复杂,这会影响增删查的性能
- 函数的返回值是int类型,并将尽可能的随机&均匀分布
- 相同的key必须要得到相同的返回,不同的key最大程度的保证其返回值不同
java的hash函数计算非常巧妙,借助位运算与hashCode实现,而不是直接取模:
static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);}
哈希冲突解决方式
hash冲突:两个不同的key经过hash函数计算之后,得到了同样的位置,这时应该如何存储。
1.开放寻址法
假如hash计算之后,应该存储与第3个位置,但是第三个位置已经有元素,这时候进行加1,即看看第4个位置是否有元素;依次进行直到找到空闲位置。
除了加1之外,还可以加1^2,如果被占用,可以加2^2;依次类推...
或者是经过二次hash等;
开放寻址法的缺点是,但查找元素时,如果hash计算的位置有元素,不一定是查找的元素,需要进行对比;如果不是,则需要按照开放寻址的思路,直到查找至空闲位置为止;
另外删除元素时不能直接删除,否则上面的查找逻辑就会出问题,需要标记为删除。
2. 拉链法
遇见冲突之后,采用链表数据结构;如下图所示:
这时查找元素时,首先确定对应位置;然后进行链表遍历。
扩容机制
关于装载因子,指的是数组已使用的个数与数组长度比值,比如数组长度是10,已经有6个位置放置元素,那么装载因子是6/10 = 0.6;
当装载因子大于设定的阈值之后,需要扩容;
哈希表的扩容不同于一般的数据扩容,需要涉及到扩容之后位置的重新计算
java语言中采用的是,当需要扩容时,直接扩容至当前容量的两倍;
扩容时需要注意不要超过最大容量;
核心代码如下:
... ...else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) newThr = oldThr << 1; // double threshold... ...Node[] newTab = (Node[])new Node[newCap];table = newTab;...//新位置计算newTab[e.hash & (newCap - 1)] = e;
实际上,插入元素如果遇见扩容时,效率时非常慢的;
====
技术连载:开篇词
技术连载:连载提纲设计思路
技术连载:数据结构 - 数组
技术连载:数据结构 - 数组常见面试题汇总
技术连载:数据结构 - 链表
技术连载:数据结构 - 链表相关的高频面试题汇总
技术连载:数据结构 - 栈
技术连载:数据结构 - 栈在面试中的应用
技术连载:数据结构 - 队列及其变种(循环、双端、阻塞、并发)
技术连载:数据结构 - 从源码出发分析java中的队列实现