为什么出现哈希表?
在之前的数据结构如数组、链表中,如果通过键值查找数据是很消耗内存的事,因为必须通过线性查找,对每一项进行遍历。而如果只是通过索引查找就会简单的多。因此哈希表就将键值与索引建立了联系。让我们通过某个键值,得到其索引,再通过索引很方便地查找某项数据。
怎么建立联系呢?
当当当~哈希函数应运而生,哈希函数将键值(一般为字符串、字母)转换为数字,一般是通过对字符进行编码转换实现。但是如果只是进行简单的转换为编码,不同字符串转换后结果相同的概率就很大。因此会对字符串的编码(如ASCII码进行幂次转换)得到一个大数字。那么问题又来了,如果直接使用大数字作为索引将数据插入数组中,那么这个哈希表的长度就会很长,而利用率却不高。因此就会对转换后的大数字进行压缩,也就是哈希化。转换生成一个小数字,再作为数据的索引。
优秀的哈希函数?
- 应该可以快速计算得到转换后的值,复杂度不能太高,尽量少使用乘除。比较有名的就是霍纳法则(秦九韶算法)--- hashcode = 37 *
- 使数据均匀分布。这样可以有效地减少探测次数
冲突
当字符串转换最后得到的索引值相同时,就会产生冲突,如果直接进行操作就会出错,那么如何解决冲突呢?
链地址法
数组中,每一项不再只存储一条数据,而是存储转换后索引相同的所有数据。因此这一项的数据类型就要改变,可以使用数组或者链表,两者的性能是差不多的,都是使用线性查找。
在数组中,存储了多个键值对(数组)。
开放地址法
按照将字符串转换后的索引存放数据,如果该索引已有数据,则向后查找空白位置填入。而查找空白位置的方式有:线性探测、二次探测和再哈希法。
线性探测
顾名思义就是指,由当前索引,每次向后移动一位进行查找。
在插入时以步长为1寻找空白位置插入;
在查找数据时,如果原索引已有数据,以步长为1向后寻找数据,如果碰到空白位置却仍然未找到数据,则说明没有该数据。
在删除数据时,不能将该索引内容设为null。会造成下一次查找数据时,遍历到此位置为空白,不再继续查找后面的数据。而要查找的数据其实在空白位置后面。
聚集:当连续插入多个类似21/22/23等数据时,会造成数据都存储在连续位置上,形成一连串的填充单元,造成下一次插入相同索引的数据时需要探测很多次。
二次探测
为了解决聚集造成的探测效率低的问题,更改步长为x+1^2 x+2^2 .. 但是这样如果连续插入转换后索引相同的数据时,依然造成聚集
再哈希法
再哈希法将探测步长与关键字相联系。对关键字再次进行哈希函数处理,这样不同关键字即使索引相同,步长也不同。
注意:这里的哈希函数不能与之前的哈希函数结果相同,不能输出为0。( stepSize = constant - (key % constant) )constant为质数
填充因子
填充因子 = 哈希表包含的数据项 / 整个哈希表长度
填充因子越大,探测效率越低
开放地址法的增删改查耗时随着填充因子的增大,呈指数型增长;而链地址法的耗时呈线性增长,所以推荐使用链地址法
哈希表的缺点
- 空间利用率低
- 元素是无序的
- 很难查找哈希表中的最大值和最小值